├── .editorconfig ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE ├── labels.json └── workflows │ ├── codeql.yml │ ├── idle.yml │ ├── set-default-labels.yml │ └── welcome-bot.yml ├── .prettierignore ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── REVIEWING.md ├── audio-analyser ├── index.html └── viper.mp3 ├── audio-basics ├── index.html ├── outfoxing.mp3 └── style.css ├── audio-buffer-source-node ├── loop │ ├── index.html │ ├── rnb-lofi-melody-loop.wav │ └── script.js └── playbackrate │ ├── index.html │ ├── rnb-lofi-melody-loop.wav │ └── script.js ├── audio-buffer └── index.html ├── audio-param ├── index.html ├── viper.mp3 └── viper.ogg ├── audiocontext-states └── index.html ├── audioworklet ├── hiss-generator.js ├── index.html ├── script.js └── style.css ├── compressor-example ├── index.html ├── viper.mp3 └── viper.ogg ├── create-media-stream-destination └── index.html ├── decode-audio-data ├── callback │ ├── index.html │ ├── script.js │ ├── viper.mp3 │ └── viper.ogg └── promise │ ├── index.html │ ├── script.js │ └── viper.mp3 ├── iirfilter-node ├── index.html └── outfoxing.mp3 ├── media-source-buffer ├── index.html ├── viper.mp3 └── viper.ogg ├── multi-track ├── bassguitar.mp3 ├── clav.mp3 ├── drums.mp3 ├── horns.mp3 ├── index.html ├── leadguitar.mp3 └── style.css ├── offline-audio-context-promise ├── index.html ├── script.js └── viper.ogg ├── offline-audio-context ├── index.html └── viper.ogg ├── output-timestamp ├── index.html └── outfoxing.mp3 ├── panner-node ├── icons │ ├── boom.svg │ ├── left.svg │ ├── right.svg │ ├── zoom-in.svg │ └── zoom-out.svg ├── index.html ├── main.js ├── style.css └── viper.ogg ├── script-processor-node ├── index.html ├── script.js └── viper.ogg ├── spatialization ├── index.html ├── outfoxing.mp3 └── style.css ├── step-sequencer ├── dtmf.mp3 ├── index.html ├── style.css └── wavetable.js ├── stereo-panner-node ├── index.html ├── viper.mp3 └── viper.ogg ├── stream-source-buffer └── index.html ├── violent-theremin ├── .htaccess ├── README.md ├── app-icons │ ├── icon-128.png │ ├── icon-16.png │ └── icon-60.png ├── favicon.ico ├── index.html ├── manifest.webapp ├── scripts │ ├── app.js │ └── respond.js └── styles │ ├── app.css │ └── normalize.css └── voice-change-o-matic ├── README.md ├── app-icons ├── icon-128.png ├── icon-16.png └── icon-60.png ├── audio ├── concert-crowd.mp3 └── concert-crowd.ogg ├── favicon.ico ├── images └── pattern.png ├── index.html ├── manifest.webapp ├── scripts ├── app.js └── install.js └── styles ├── app.css ├── install-button.css └── normalize.css /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file is used to request PR reviews from the appropriate team. 2 | 3 | # Default 4 | * @mdn/core-yari-content 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: "Issue report" 2 | description: Report an unexpected problem or unintended behavior. 3 | labels: ["needs triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ### Before you start 9 | 10 | **Want to fix the problem yourself?** This project is open source and we welcome fixes and improvements from the community! 11 | 12 | ↩ Check the project [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) guide to see how to get started. 13 | 14 | --- 15 | - type: textarea 16 | id: problem 17 | attributes: 18 | label: What information was incorrect, unhelpful, or incomplete? 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: expected 23 | attributes: 24 | label: What did you expect to see? 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: references 29 | attributes: 30 | label: Do you have any supporting links, references, or citations? 31 | description: Link to information that helps us confirm your issue. 32 | - type: textarea 33 | id: more-info 34 | attributes: 35 | label: Do you have anything more you want to share? 36 | description: For example, steps to reproduce, screenshots, screen recordings, or sample code. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Content or feature request 4 | url: https://github.com/mdn/mdn/issues/new/choose 5 | about: Propose new content for MDN Web Docs or submit a feature request using this link. 6 | - name: MDN GitHub Discussions 7 | url: https://github.com/orgs/mdn/discussions 8 | about: Does the issue involve a lot of changes, or is it hard to split it into actionable tasks? Start a discussion before opening an issue. 9 | - name: MDN Web Docs on Discourse 10 | url: https://discourse.mozilla.org/c/mdn/learn/250 11 | about: Need help with assessments on MDN Web Docs? We have a support community for this purpose on Discourse. 12 | - name: Help with code 13 | url: https://stackoverflow.com/ 14 | about: If you are stuck and need help with code, StackOverflow is a great resource. 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Description 4 | 5 | 6 | 7 | ### Motivation 8 | 9 | 10 | 11 | ### Additional details 12 | 13 | 14 | 15 | ### Related issues and pull requests 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/labels.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "good first issue", 4 | "color": "028c46", 5 | "description": "A good issue for newcomers to get started with." 6 | }, 7 | { 8 | "name": "help wanted", 9 | "color": "028c46", 10 | "description": "If you know something about this, we would love your help!" 11 | }, 12 | { 13 | "name": "needs info", 14 | "color": "028c46", 15 | "description": "This needs more information to review or act on." 16 | }, 17 | { 18 | "name": "needs triage", 19 | "color": "028c46", 20 | "description": "Triage needed by staff and/or partners. Automatically applied when an issue is opened." 21 | }, 22 | { 23 | "name": "expert help needed", 24 | "color": "028c46", 25 | "description": "This needs more information from a subject matter expert (SME)." 26 | }, 27 | { 28 | "name": "idle", 29 | "color": "028c46", 30 | "description": "Issues and pull requests with no activity for three months." 31 | }, 32 | { 33 | "name": "on hold", 34 | "color": "028c46", 35 | "description": "Waiting on something else before this can be moved forward." 36 | }, 37 | { 38 | "name": "for later", 39 | "color": "028c46", 40 | "description": "Not planned at this time." 41 | }, 42 | { 43 | "name": "needs content update", 44 | "color": "028c46", 45 | "description": "Needs update to the content to support this change." 46 | }, 47 | { 48 | "name": "chore", 49 | "color": "028c46", 50 | "description": "A routine task." 51 | }, 52 | { 53 | "name": "enhancement", 54 | "color": "028c46", 55 | "description": "Improves an existing feature." 56 | }, 57 | { 58 | "name": "bug", 59 | "color": "c05964", 60 | "description": "Indicates an unexpected problem or unintended behavior." 61 | }, 62 | { 63 | "name": "wontfix", 64 | "color": "c05964", 65 | "description": "Deemed to be outside the scope of the project or would require significant time and resources to fix." 66 | }, 67 | { 68 | "name": "effort: small", 69 | "color": "866dc1", 70 | "description": "Task is a small effort." 71 | }, 72 | { 73 | "name": "effort: medium", 74 | "color": "866dc1", 75 | "description": "Task is a medium effort." 76 | }, 77 | { 78 | "name": "effort: large", 79 | "color": "866dc1", 80 | "description": "Task is large effort." 81 | }, 82 | { 83 | "name": "p0", 84 | "color": "F9D0C4", 85 | "description": "Urgent. We will address this as soon as possible." 86 | }, 87 | { 88 | "name": "p1", 89 | "color": "FEF2C0", 90 | "description": "We will address this soon and will provide capacity from our team for it in the next few releases." 91 | }, 92 | { 93 | "name": "p2", 94 | "color": "C2E0C6", 95 | "description": "We want to address this but may have other higher priority items." 96 | }, 97 | { 98 | "name": "p3", 99 | "color": "BFDADC", 100 | "description": "We don't have visibility when this will be addressed." 101 | } 102 | ] 103 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | paths-ignore: 7 | - "**.md" 8 | pull_request: 9 | # The branches below must be a subset of the branches above 10 | branches: ["main"] 11 | paths-ignore: 12 | - "**.md" 13 | 14 | jobs: 15 | analyze: 16 | name: Analyze 17 | runs-on: ubuntu-latest 18 | permissions: 19 | actions: read 20 | contents: read 21 | security-events: write 22 | 23 | strategy: 24 | matrix: 25 | # Add the language(s) you want to analyze here as an array of strings 26 | # for example: ['javascript'] or ['python', 'javascript'] 27 | language: ["javascript"] 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v3 32 | 33 | # Initializes the CodeQL tools for scanning. 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v2 36 | with: 37 | languages: ${{ matrix.language }} 38 | 39 | - name: Perform CodeQL Analysis 40 | uses: github/codeql-action/analyze@v2 41 | with: 42 | category: "/language:${{matrix.language}}" 43 | -------------------------------------------------------------------------------- /.github/workflows/idle.yml: -------------------------------------------------------------------------------- 1 | # This workflow is hosted at: https://github.com/mdn/workflows/blob/main/.github/workflows/idle.yml 2 | # Docs for this workflow: https://github.com/mdn/workflows/blob/main/README.md#idle 3 | name: "Label idle issues" 4 | 5 | on: 6 | schedule: 7 | - cron: "0 8 * * *" 8 | 9 | jobs: 10 | mark-as-idle: 11 | uses: mdn/workflows/.github/workflows/idle.yml@main 12 | with: 13 | target-repo: "mdn/webaudio-examples" 14 | -------------------------------------------------------------------------------- /.github/workflows/set-default-labels.yml: -------------------------------------------------------------------------------- 1 | name: set-default-labels 2 | on: [workflow_dispatch] 3 | 4 | jobs: 5 | set-default-labels: 6 | uses: mdn/workflows/.github/workflows/set-default-labels.yml@main 7 | with: 8 | target-repo: "mdn/webaudio-examples" 9 | should-delete-labels: true 10 | -------------------------------------------------------------------------------- /.github/workflows/welcome-bot.yml: -------------------------------------------------------------------------------- 1 | # This workflow is hosted at: https://github.com/mdn/workflows/blob/main/.github/workflows/allo-allo.yml 2 | # Docs for this workflow: https://github.com/mdn/workflows/blob/main/README.md#allo-allo 3 | name: "AlloAllo" 4 | 5 | on: 6 | issues: 7 | types: 8 | - opened 9 | pull_request_target: 10 | branches: 11 | - main 12 | types: 13 | - opened 14 | - closed 15 | 16 | jobs: 17 | allo-allo: 18 | uses: mdn/workflows/.github/workflows/allo-allo.yml@main 19 | with: 20 | target-repo: "mdn/webaudio-examples" 21 | issue-welcome: | 22 | It looks like this is your first issue. Welcome! 👋 23 | One of the project maintainers will be with you as soon as possible. We 24 | appreciate your patience. To safeguard the health of the project, please 25 | take a moment to read our [code of conduct](../blob/main/CODE_OF_CONDUCT.md). 26 | pr-welcome: | 27 | It looks like this is your first pull request. 🎉 28 | Thank you for your contribution! One of the project maintainers will triage 29 | and assign the pull request for review. We appreciate your patience. To 30 | safeguard the health of the project, please take a moment to read our 31 | [code of conduct](../blob/main/CODE_OF_CONDUCT.md). 32 | pr-merged: | 33 | Congratulations on your first merged pull request. 🎉 Thank you for your contribution! 34 | Did you know we have a [project board](https://github.com/orgs/mdn/projects/25) with high-impact contribution opportunities? 35 | We look forward to your next contribution. 36 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | violent-theremin/scripts/respond.js 2 | voice-change-o-matic/scripts/respond.js 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSameLine": true 3 | } 4 | -------------------------------------------------------------------------------- /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 | 9 | 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. 10 | 11 | 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guide 2 | 3 | ![github-profile](https://user-images.githubusercontent.com/10350960/166113119-629295f6-c282-42c9-9379-af2de5ad4338.png) 4 | 5 | - [Ways to contribute](#ways-to-contribute) 6 | - [Finding an issue](#finding-an-issue) 7 | - [Asking for help](#asking-for-help) 8 | - [Pull request process](#pull-request-process) 9 | - [Forking and cloning the project](#forking-and-cloning-the-project) 10 | - [Signing commits](#signing-commits) 11 | 12 | Welcome 👋 Thank you for your interest in contributing to MDN Web Docs. We are happy to have you join us! 💖 13 | 14 | As you get started, you are in the best position to give us feedback on project areas we might have forgotten about or assumed to work well. 15 | These include, but are not limited to: 16 | 17 | - Problems found while setting up a new developer environment 18 | - Gaps in our documentation 19 | - Bugs in our automation scripts 20 | 21 | If anything doesn't make sense or work as expected, please open an issue and let us know! 22 | 23 | ## Ways to contribute 24 | 25 | We welcome many different types of contributions including: 26 | 27 | - New features and content suggestions. 28 | - Identifying and filing issues. 29 | - Providing feedback on existing issues. 30 | - Engaging with the community and answering questions. 31 | - Contributing documentation or code. 32 | - Promoting the project in personal circles and social media. 33 | 34 | ## Finding an issue 35 | 36 | We have issues labeled `good first issue` for new contributors and `help wanted` suitable for any contributor. 37 | Good first issues have extra information to help you make your first contribution a success. 38 | Help wanted issues are ideal when you feel a bit more comfortable with the project details. 39 | 40 | Sometimes there won't be any issues with these labels, but there is likely still something for you to work on. 41 | If you want to contribute but don't know where to start or can't find a suitable issue, speak to us on [Matrix](https://matrix.to/#/#mdn:mozilla.org), and we will be happy to help. 42 | 43 | Once you find an issue you'd like to work on, please post a comment saying you want to work on it. 44 | Something like "I want to work on this" is fine. 45 | Also, mention the community team using the `@mdn/mdn-community-engagement` handle to ensure someone will get back to you. 46 | 47 | ## Asking for help 48 | 49 | The best way to reach us with a question when contributing is to use the following channels in the following order of precedence: 50 | 51 | - [Start a discussion](https://github.com/orgs/mdn/discussions) 52 | - Ask your question or highlight your discussion on [Matrix](https://matrix.to/#/#mdn:mozilla.org). 53 | - File an issue and tag the community team using the `@mdn/mdn-community-engagement` handle. 54 | 55 | ## Pull request process 56 | 57 | The MDN Web Docs project has a well-defined pull request process which is documented in the [Pull request guidelines](https://developer.mozilla.org/en-US/docs/MDN/Community/Pull_requests). 58 | Make sure you read and understand this process before you start working on a pull request. 59 | 60 | 72 | 73 | ### Forking and cloning the project 74 | 75 | The first step in setting up your development environment is to [fork the repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo) and [clone](https://docs.github.com/en/get-started/quickstart/fork-a-repo#cloning-your-forked-repository) the repository to your local machine. 76 | 77 | ## Signing commits 78 | 79 | We require all commits to be signed to verify the author's identity. 80 | GitHub has a detailed guide on [setting up signed commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). 81 | If you get stuck, please [ask for help](#asking-for-help). 82 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webaudio-examples 2 | 3 | Code examples that accompany the [MDN Web Audio documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API). 4 | 5 | ## Serving the examples 6 | 7 | To preview the examples, clone the repository and navigate to the example you want to view. 8 | For example, if you have Python installed, you can use the following commands to serve the `audio-analyser` example: 9 | 10 | ```bash 11 | cd audio-analyser 12 | python3 -m http.server 13 | ``` 14 | 15 | > [!NOTE] 16 | > If you're using the built-in Python HTTP server, be sure to use at least [Python version `3.10.12`](https://www.python.org/downloads/release/python-31012/). 17 | 18 | Then navigate to `http://localhost:8000` in your browser. 19 | 20 | For more information on serving files locally using different languages or technologies, see [Running a simple local HTTP server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server#running_a_simple_local_http_server). 21 | 22 | ## Repository contents 23 | 24 | ### Audio analyser 25 | 26 | The [audio-analyser](https://github.com/mdn/webaudio-examples/tree/main/audio-analyser) directory contains a very simple example showing a graphical visualization of an audio signal drawn with data taken from an [`AnalyserNode`](https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode) interface. [Run the demo live](http://mdn.github.io/webaudio-examples/audio-analyser/). 27 | 28 | ### Audio basics 29 | 30 | The [audio-basics](https://github.com/mdn/webaudio-examples/tree/main/audio-basics) directory contains a fun example showing a retro-style "boombox" that allows audio to be played, stereo-panned, and volume-adjusted. [Run the demo live](http://mdn.github.io/webaudio-examples/audio-basics/). 31 | 32 | ### Audio buffer 33 | 34 | The [audio-buffer](https://github.com/mdn/webaudio-examples/tree/main/audio-buffer) directory contains a very simple example showing how to use an [`AudioBuffer`](https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer) interface in the Web Audio API. [Run the demo live](http://mdn.github.io/webaudio-examples/audio-buffer/). 35 | 36 | ### Audio param 37 | 38 | The [audio-param](https://github.com/mdn/webaudio-examples/tree/main/audio-param) directory contains some simple examples showing how to use the methods of the Web Audio API [`AudioParam`](https://developer.mozilla.org/en-US/docs/Web/API/AudioParam) interface. [Run example live](http://mdn.github.io/webaudio-examples/audio-param/). 39 | 40 | ### Audio context states 41 | 42 | The [audiocontext-states](https://github.com/mdn/webaudio-examples/tree/main/audiocontext-states) directory contains a simple demo of the new Web Audio API [`AudioContext`](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext) methods, including the `states` property and the `close()`, `resume()`, and `suspend()` methods. [Run the demo live](http://mdn.github.io/webaudio-examples/audiocontext-states/). 43 | 44 | ### Audio worklet 45 | 46 | The [audioworklet](https://github.com/mdn/webaudio-examples/tree/main/audioworklet) directory contains an example showing how to use the [`AudioWorklet`](https://developer.mozilla.org/en-US/docs/Web/API/AudioWorklet) interface. See also the guide on [background audio processing using AudioWorklet](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Using_AudioWorklet). [Run the example live](http://mdn.github.io/webaudio-examples/audioworklet/). 47 | 48 | ### Compressor example 49 | 50 | The [compressor-example](https://github.com/mdn/webaudio-examples/tree/main/compressor-example) directory contains a simple demo to show usage of the Web Audio API [`BaseAudioContext.createDynamicsCompressor()`](https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/createDynamicsCompressor) method and [`DynamicsCompressorNode`](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode) interface. [Run the example live](http://mdn.github.io/webaudio-examples/compressor-example/). 51 | 52 | ### Create media stream destination 53 | 54 | The [create-media-stream-destination](https://github.com/mdn/webaudio-examples/tree/main/create-media-stream-destination) directory contains a simple example showing how the Web Audio API [`AudioContext.createMediaStreamDestination()`](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createMediaStreamDestination) method can be used to output a stream - in this case to a [`MediaRecorder`](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder) instance - to output a sinewave to an [opus](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Audio_codecs#Opus) file. [Run the demo live](http://mdn.github.io/webaudio-examples/create-media-stream-destination/). 55 | 56 | ### Decode audio data 57 | 58 | The [decode-audio-data](https://github.com/mdn/webaudio-examples/tree/main/decode-audio-data) directory contains a simple example demonstrating usage of the Web Audio API [`BaseAudioContext.decodeAudioData()`](https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData) method. [View example live](http://mdn.github.io/webaudio-examples/decode-audio-data/promise). 59 | 60 | ### IIR filter node 61 | 62 | The [iirfilter-node](https://github.com/mdn/webaudio-examples/tree/main/iirfilter-node) directory contains an example showing usage of an [`IIRFilterNode`](https://developer.mozilla.org/en-US/docs/Web/API/IIRFilterNode) interface. [Run the demo live](http://mdn.github.io/webaudio-examples/iirfilter-node/). 63 | 64 | ### Media source buffer 65 | 66 | The [media-source-buffer](https://github.com/mdn/webaudio-examples/tree/main/media-source-buffer) directory contains a simple example demonstrating usage of the Web Audio API [`AudioContext.createMediaElementSource()`](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createMediaElementSource) method. [View the demo live](http://mdn.github.io/webaudio-examples/media-source-buffer/). 67 | 68 | ### Multi track 69 | 70 | The [multi-track](https://github.com/mdn/webaudio-examples/tree/main/multi-track) directory contains an example of connecting separate independently-playable audio tracks to a single [`AudioDestinationNode`](https://developer.mozilla.org/en-US/docs/Web/API/AudioDestinationNode) interface. [Run the example live](http://mdn.github.io/webaudio-examples/multi-track/). 71 | 72 | ### Offline audio context 73 | 74 | The [offline-audio-context](https://github.com/mdn/webaudio-examples/tree/main/offline-audio-context) directory contains a simple example to show how a Web Audio API [`OfflineAudioContext`](https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext) interface can be used to rapidly process/render audio in the background to create a buffer, which can then be used in any way you please. For more information, see [https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext](https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext). [Run example live](http://mdn.github.io/webaudio-examples/offline-audio-context/). 75 | 76 | ### Offline audio context promise 77 | 78 | The [offline-audio-context-promise](https://github.com/mdn/webaudio-examples/tree/main/offline-audio-context-promise) directory contains a simple example to show how a Web Audio API [`OfflineAudioContext`](https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext) interface can be used to rapidly process/render audio in the background to create a buffer, which can then be used in any way you please. [Run the example live](http://mdn.github.io/webaudio-examples/offline-audio-context-promise/). 79 | 80 | ### Output timestamp 81 | 82 | The [output-timestamp](https://github.com/mdn/webaudio-examples/tree/main/output-timestamp) directory contains an example of how the [`AudioContext.getOutputTimestamp()`](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/getOutputTimestamp) property can be used to log `contextTime` and `performanceTime` to the console. [Try the demo live](https://mdn.github.io/webaudio-examples/output-timestamp/). 83 | 84 | ### Panner node 85 | 86 | The [panner-node](https://github.com/mdn/webaudio-examples/tree/main/panner-node) directory contains a demo to show basic usage of the Web Audio API [`BaseAudioContext.createPanner()`](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createPanner) method to control audio spatialization. [Run the example live](http://mdn.github.io/webaudio-examples/panner-node/). 87 | 88 | ### Script processor node 89 | 90 | The [script-processor-node](https://github.com/mdn/webaudio-examples/tree/main/script-processor-node) directory contains a simple demo showing how to use the Web Audio API's [`ScriptProcessorNode`](https://developer.mozilla.org/en-US/docs/Web/API/ScriptProcessorNode) interface to process a loaded audio track, adding a little bit of white noise to each audio sample. See the [live demo](http://mdn.github.io/webaudio-examples/script-processor-node/). 91 | 92 | ### Spatialization 93 | 94 | The [spatialization](https://github.com/mdn/webaudio-examples/tree/main/spatialization) directory contains an example of how the various properties of a [`PannerNode`](https://developer.mozilla.org/en-US/docs/Web/API/PannerNode) interface can be adjusted to emulate sound in a three-dimensional space. For more information see [Web audio spatialization basics](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Web_audio_spatialization_basics). Try the [live demo](http://mdn.github.io/webaudio-examples/spatialization/). 95 | 96 | ### Step sequencer 97 | 98 | The [step-sequencer](https://github.com/mdn/webaudio-examples/tree/main/step-sequencer) directory contains a simple step-sequencer that loops and manipulates sounds based on a dial-up modem. For more information see [Advanced techniques: creating sound, sequencing, timing, scheduling](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Advanced_techniques). See the [live demo](http://mdn.github.io/webaudio-examples/step-sequencer/) also. 99 | 100 | ### Stereo panner node 101 | 102 | The [stereo-panner-node](https://github.com/mdn/webaudio-examples/tree/main/stereo-panner-node) directory contains a simple example to show how the Web Audio API [`StereoPannerNode`](https://developer.mozilla.org/en-US/docs/Web/API/StereoPannerNode) interface can be used to pan an audio stream. [Run the example live](http://mdn.github.io/webaudio-examples/stereo-panner-node/). 103 | 104 | ### Stream source buffer 105 | 106 | The [stream-source-buffer](https://github.com/mdn/webaudio-examples/tree/main/stream-source-buffer) directory contains a simple example demonstrating usage of the Web Audio API [`MediaStreamAudioSourceNode`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamAudioSourceNode) object. [View example live](http://mdn.github.io/webaudio-examples/stream-source-buffer/). 107 | 108 | ### Violent theremin 109 | 110 | Violent theremin uses the Web Audio API to generate sound, and HTML5 canvas for a bit of pretty visualization. The colors generated depend on the pitch and gain of the current note, which are themselves dependent on the mouse pointer position. 111 | 112 | You can [view the demo](http://mdn.github.io/webaudio-examples/violent-theremin/) live here. 113 | 114 | ### Voice-change-O-matic 115 | 116 | The [voice-change-o-matic](https://github.com/mdn/webaudio-examples/tree/main/voice-change-o-matic) directory contains a Web Audio API-powered voice changer and visualizer. 117 | 118 | You can [view the demo](http://mdn.github.io/webaudio-examples/voice-change-o-matic/) live here. 119 | -------------------------------------------------------------------------------- /REVIEWING.md: -------------------------------------------------------------------------------- 1 | # Reviewing guide 2 | 3 | ## Values and guidelines 4 | 5 | All reviewers must abide by the [Code of Conduct](CODE_OF_CONDUCT.md); they are also protected by the Code of Conduct. 6 | A reviewer should not tolerate poor behavior and is encouraged to [report any behavior](CODE_OF_CONDUCT.md#Reporting_violations) that violates the Code of Conduct. 7 | 8 | Everyone participating must follow our [Community Participation Guidelines](CODE_OF_CONDUCT.md#Community_Participation_Guidelines) 9 | 10 | ## Review process 11 | 12 | The MDN Web Docs team has a well-defined review process that must be followed by reviewers in all repositories under the GitHub MDN organization. 13 | This process is described in detail on the [Pull request guidelines](https://developer.mozilla.org/en-US/docs/MDN/Community/Pull_requests) page. 14 | 15 | 22 | -------------------------------------------------------------------------------- /audio-analyser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web Audio API examples: audio analyser 8 | 9 | 14 | 15 | 16 | 17 |

Web Audio API examples: audio analyser

18 | 19 | 20 |
21 | 22 |     23 | 24 |

25 | 26 |
27 | 28 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /audio-analyser/viper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/audio-analyser/viper.mp3 -------------------------------------------------------------------------------- /audio-basics/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web Audio API examples: Basics 6 | 7 | 11 | 12 | 13 | 14 |
15 |
16 | 17 |
18 |
19 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 |
58 | 59 | 60 | 61 | 62 | 70 |
71 |
72 | 73 |
74 | 75 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /audio-basics/outfoxing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/audio-basics/outfoxing.mp3 -------------------------------------------------------------------------------- /audio-basics/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --orange: hsla(32, 100%, 50%, 1); 3 | --yellow: hsla(49, 99%, 50%, 1); 4 | --lime: hsla(82, 90%, 45%, 1); 5 | --green: hsla(127, 81%, 41%, 1); 6 | --red: hsla(342, 93%, 53%, 1); 7 | --pink: hsla(314, 85%, 45%, 1); 8 | --blue: hsla(211, 92%, 52%, 1); 9 | --purple: hsla(283, 92%, 44%, 1); 10 | --cyan: hsla(195, 98%, 55%, 1); 11 | --white: hsla(0, 0%, 95%, 1); 12 | --black: hsla(0, 0%, 10%, 1); 13 | 14 | /* abstract our colours */ 15 | --boxMain: var(--blue); 16 | --boxSecond: var(--cyan); 17 | --boxHigh: var(--orange); 18 | 19 | --border: 1vmin solid var(--black); 20 | --borderRad: 2px; 21 | } 22 | * { 23 | box-sizing: border-box; 24 | } 25 | body { 26 | background-color: var(--white); 27 | padding: 4vmax; 28 | font-family: system-ui; 29 | color: var(--black); 30 | } 31 | 32 | #boombox { 33 | width: 82vw; 34 | max-width: 800px; 35 | margin: 0px auto; 36 | } 37 | @media screen and (max-width: 570px) { 38 | #boombox { 39 | max-width: 600px; 40 | } 41 | } 42 | .boombox-handle { 43 | margin: 0px 5vw; 44 | height: 12vh; 45 | background: var(--white) 46 | linear-gradient( 47 | 180deg, 48 | var(--boxHigh) 4vmin, 49 | var(--black) 4vmin, 50 | var(--black) 5vmin, 51 | var(--white) 5vmin 52 | ) 53 | no-repeat; 54 | border: var(--border); 55 | border-bottom-width: 0px; 56 | border-radius: var(--borderRad); 57 | } 58 | .boombox-body { 59 | /*gradient with two circles for speakers*/ 60 | /* padding-top: 3vh; */ 61 | background: var(--boxMain) repeat-x bottom left; 62 | background-image: radial-gradient( 63 | circle, 64 | var(--boxMain) 2vmin, 65 | var(--black) 2vmin, 66 | var(--black) 3vmin, 67 | var(--boxSecond) 3vmin, 68 | var(--boxSecond) 9vmin, 69 | var(--black) 9vmin, 70 | var(--black) 9.5vmin, 71 | var(--boxSecond) 9.5vmin, 72 | var(--boxSecond) 12vmin, 73 | var(--black) 12vmin, 74 | var(--black) 13vmin, 75 | var(--boxMain) 13vmin 76 | ); 77 | background-size: 33.3% 70%; 78 | border: 6px solid var(--black); 79 | border-radius: var(--borderRad); 80 | } 81 | /*small screen with one circle*/ 82 | @media screen and (max-width: 570px) { 83 | .boombox-body { 84 | background-repeat: no-repeat; 85 | background-position: center top; 86 | background-size: 70% 70%; 87 | background-image: radial-gradient( 88 | circle, 89 | var(--boxMain) 4vmin, 90 | var(--black) 4vmin, 91 | var(--black) 5vmin, 92 | var(--boxSecond) 5vmin, 93 | var(--boxSecond) 21vmin, 94 | var(--black) 21vmin, 95 | var(--black) 21.5vmin, 96 | var(--boxSecond) 21.5vmin, 97 | var(--boxSecond) 24vmin, 98 | var(--black) 24vmin, 99 | var(--black) 25vmin, 100 | var(--boxMain) 25vmin 101 | ); 102 | } 103 | } 104 | 105 | .master-controls { 106 | display: grid; 107 | grid-template-rows: repeat(2, auto); 108 | grid-template-columns: repeat(2, 1fr); 109 | /*name our grid areas so they are more manipulative later*/ 110 | grid-template-areas: 111 | "volin panin power" 112 | "vollab panlab power"; 113 | justify-items: center; 114 | align-items: center; 115 | padding: 2vmax; 116 | background-color: var(--boxSecond); 117 | border-bottom: var(--border); 118 | } 119 | /* change control grid areas for smaller boom box */ 120 | @media screen and (max-width: 570px) { 121 | .master-controls { 122 | grid-gap: 10px; 123 | grid-template-columns: 16% 1fr 12%; 124 | grid-template-areas: 125 | "vollab volin power" 126 | "panlab panin power"; 127 | } 128 | } 129 | 130 | .master-controls input, 131 | .master-controls label { 132 | display: block; 133 | } 134 | .master-controls input::before, 135 | .master-controls input::after { 136 | line-height: 5vh; 137 | color: var(--black); 138 | } 139 | .master-controls input::before { 140 | padding-right: 1vw; 141 | } 142 | .master-controls input::after { 143 | padding-left: 1vw; 144 | } 145 | 146 | .control-volume { 147 | grid-area: volin; 148 | } 149 | .control-volume::before { 150 | content: "min"; 151 | } 152 | .control-volume::after { 153 | content: "max"; 154 | } 155 | label[for="volume"] { 156 | grid-area: vollab; 157 | margin-top: 15px; 158 | } 159 | .control-panner { 160 | grid-area: panin; 161 | } 162 | .control-panner::before { 163 | content: "left"; 164 | } 165 | .control-panner::after { 166 | content: "right"; 167 | } 168 | label[for="panner"] { 169 | grid-area: panlab; 170 | margin-top: 15px; 171 | } 172 | @media screen and (max-width: 570px) { 173 | label[for="volume"], 174 | label[for="panner"] { 175 | margin-top: 0px; 176 | } 177 | .control-volume { 178 | margin-bottom: 20px; 179 | } 180 | } 181 | 182 | .control-power { 183 | grid-area: power; 184 | align-self: center; 185 | width: 40px; 186 | height: 40px; 187 | border: 3px solid var(--black); 188 | border-radius: 20px; 189 | cursor: pointer; 190 | } 191 | .control-power span { 192 | display: none; 193 | } 194 | 195 | /* tape area ~~~~~~~~~~~~~~~~~~~~~~ */ 196 | .tape { 197 | display: grid; 198 | grid-template-rows: 24vh 6vh; 199 | grid-template-columns: repeat(5, 1fr); 200 | grid-template-areas: 201 | "tape tape tape tape tape" 202 | "back rewind play ff next"; 203 | 204 | width: 26vw; 205 | margin: 0px auto; 206 | background: var(--boxMain) no-repeat center center; 207 | background-image: radial-gradient( 208 | circle at 30%, 209 | var(--boxSecond) 2vh, 210 | var(--black) 2vh, 211 | var(--black) 2.5vh, 212 | transparent 2.5vh 213 | ), 214 | radial-gradient( 215 | circle at 70%, 216 | var(--boxSecond) 2vh, 217 | var(--black) 2vh, 218 | var(--black) 2.5vh, 219 | transparent 2.5vh 220 | ), 221 | linear-gradient( 222 | 180deg, 223 | transparent 9.1vh, 224 | var(--black) 9.1vh, 225 | var(--black) 10.1vh, 226 | var(--boxHigh) 10.1vh, 227 | var(--boxHigh) 20vh, 228 | var(--black) 20vh, 229 | var(--black) 21vh, 230 | transparent 21vh 231 | ), 232 | radial-gradient( 233 | circle at 30%, 234 | var(--boxHigh) 5vh, 235 | var(--black) 5vh, 236 | var(--black) 6vh, 237 | transparent 6vh 238 | ), 239 | radial-gradient( 240 | circle at 70%, 241 | var(--boxHigh) 5vh, 242 | var(--black) 5vh, 243 | var(--black) 6vh, 244 | transparent 6vh 245 | ); 246 | background-size: 100% 100%, 100% 100%, 40% 100%, 100% 100%, 100% 100%; 247 | border: var(--border); 248 | border-bottom-width: 0px; 249 | border-radius: var(--borderRad); 250 | max-width: 300px; 251 | } 252 | @media screen and (max-width: 570px) { 253 | .tape { 254 | width: 80%; 255 | margin-top: 30vh; 256 | } 257 | } 258 | audio { 259 | grid-area: tape; 260 | } 261 | /*TODO GIVE BUTTONS CLASSES*/ 262 | [class^="tape-controls"] { 263 | border: none; 264 | } 265 | [class^="tape-controls"] span { 266 | display: none; 267 | } 268 | .tape-controls-play { 269 | grid-area: play; 270 | } 271 | 272 | /* ~~~~~~~~~~~~~~~~ All the crazy range styling */ 273 | input[type="range"] { 274 | -webkit-appearance: none; 275 | width: 30vw; 276 | background: transparent; 277 | } 278 | /* set min & max for different screen sizes */ 279 | @media screen and (min-width: 1100px) { 280 | input[type="range"] { 281 | width: 270px; 282 | } 283 | } 284 | @media (max-width: 680px) { 285 | input[type="range"] { 286 | width: 180px; 287 | } 288 | } 289 | @media (max-width: 380px) { 290 | input[type="range"] { 291 | width: 130px; 292 | } 293 | } 294 | 295 | input[type="range"]::-ms-track { 296 | width: 100%; 297 | cursor: pointer; 298 | background: transparent; 299 | border-color: transparent; 300 | color: transparent; 301 | } 302 | input[type="range"]::-webkit-slider-thumb { 303 | -webkit-appearance: none; 304 | margin-top: -1vh; 305 | height: 4vh; 306 | width: 2vw; 307 | border: 0.5vmin solid var(--black); 308 | border-radius: var(--borderRad); 309 | background-color: var(--boxMain); 310 | cursor: pointer; 311 | } 312 | input[type="range"]::-moz-range-thumb { 313 | height: 4vh; 314 | width: 2vw; 315 | border: 0.5vmin solid var(--black); 316 | border-radius: var(--borderRad); 317 | background-color: var(--boxMain); 318 | cursor: pointer; 319 | } 320 | input[type="range"]::-ms-thumb { 321 | height: 4vh; 322 | width: 2vw; 323 | border: 0.5vmin solid var(--black); 324 | border-radius: var(--borderRad); 325 | background-color: var(--boxMain); 326 | cursor: pointer; 327 | } 328 | 329 | input[type="range"]::-webkit-slider-runnable-track { 330 | height: 2vh; 331 | cursor: pointer; 332 | background-color: var(--black); 333 | border-radius: var(--borderRad); 334 | } 335 | input[type="range"]::-moz-range-track { 336 | height: 2vh; 337 | cursor: pointer; 338 | background-color: var(--black); 339 | border-radius: var(--borderRad); 340 | } 341 | input[type="range"]::-ms-track { 342 | height: 2vh; 343 | cursor: pointer; 344 | background-color: var(--black); 345 | border-radius: var(--borderRad); 346 | } 347 | 348 | input[type="range"]:focus { 349 | outline: none; 350 | } 351 | input[type="range"]:focus::-webkit-slider-runnable-track { 352 | background: var(--boxMain); 353 | } 354 | 355 | /*~~~~~~~~~~~~~~~~ the play pause & power icons*/ 356 | [data-playing] { 357 | background: transparent 358 | url('data:image/svg+xml;charset=utf8,') 359 | no-repeat center center; 360 | background-size: 60% 60%; 361 | cursor: pointer; 362 | } 363 | [data-playing]:hover { 364 | background: transparent 365 | url('data:image/svg+xml;charset=utf8,') 366 | no-repeat center center; 367 | background-size: 60% 60%; 368 | } 369 | [data-playing="true"] { 370 | background: transparent 371 | url('data:image/svg+xml;charset=utf8,') 372 | no-repeat center center; 373 | background-size: 60% 60%; 374 | } 375 | [data-playing="true"]:hover { 376 | background: transparent 377 | url('data:image/svg+xml;charset=utf8,') 378 | no-repeat center center; 379 | background-size: 60% 60%; 380 | } 381 | [data-power] { 382 | background: var(--boxSecond) 383 | url('data:image/svg+xml; charset=utf8,') 384 | no-repeat center center; 385 | background-size: 60% 60%; 386 | } 387 | [data-power]:hover, 388 | [data-power="on"] { 389 | background: var(--boxHigh) 390 | url('data:image/svg+xml; charset=utf8,') 391 | no-repeat center center; 392 | background-size: 60% 60%; 393 | } 394 | -------------------------------------------------------------------------------- /audio-buffer-source-node/loop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web Audio API examples: looping a track 8 | 9 | 10 | 11 | 12 | 13 |

Web Audio API examples: looping a track

14 | 15 | 16 | 17 | 18 |

Set loop start and loop end

19 | 27 | Starts at 0 s. 28 |
29 | 37 | Stops at 0 s. 38 |

39 | If the stop time is lower or equal to the start time, it'll loop over the 40 | whole track. 41 |

42 | 43 | 44 | -------------------------------------------------------------------------------- /audio-buffer-source-node/loop/rnb-lofi-melody-loop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/audio-buffer-source-node/loop/rnb-lofi-melody-loop.wav -------------------------------------------------------------------------------- /audio-buffer-source-node/loop/script.js: -------------------------------------------------------------------------------- 1 | let audioCtx; 2 | let buffer; 3 | let source; 4 | 5 | const play = document.getElementById("play"); 6 | const stop = document.getElementById("stop"); 7 | 8 | const loopstartControl = document.getElementById("loopstart-control"); 9 | const loopstartValue = document.getElementById("loopstart-value"); 10 | 11 | const loopendControl = document.getElementById("loopend-control"); 12 | const loopendValue = document.getElementById("loopend-value"); 13 | 14 | async function loadAudio() { 15 | try { 16 | // Load an audio file 17 | const response = await fetch("rnb-lofi-melody-loop.wav"); 18 | // Decode it 19 | buffer = await audioCtx.decodeAudioData(await response.arrayBuffer()); 20 | const max = Math.floor(buffer.duration); 21 | loopstartControl.setAttribute("max", max); 22 | loopendControl.setAttribute("max", max); 23 | } catch (err) { 24 | console.error(`Unable to fetch the audio file. Error: ${err.message}`); 25 | } 26 | } 27 | 28 | play.addEventListener("click", async () => { 29 | if (!audioCtx) { 30 | audioCtx = new AudioContext(); 31 | await loadAudio(); 32 | } 33 | source = audioCtx.createBufferSource(); 34 | source.buffer = buffer; 35 | source.connect(audioCtx.destination); 36 | source.loop = true; 37 | source.loopStart = loopstartControl.value; 38 | source.loopEnd = loopendControl.value; 39 | source.start(); 40 | play.disabled = true; 41 | stop.disabled = false; 42 | loopstartControl.disabled = false; 43 | loopendControl.disabled = false; 44 | }); 45 | 46 | stop.addEventListener("click", () => { 47 | source.stop(); 48 | play.disabled = false; 49 | stop.disabled = true; 50 | loopstartControl.disabled = true; 51 | loopendControl.disabled = true; 52 | }); 53 | 54 | loopstartControl.addEventListener("input", () => { 55 | source.loopStart = loopstartControl.value; 56 | loopstartValue.textContent = loopstartControl.value; 57 | }); 58 | 59 | loopendControl.addEventListener("input", () => { 60 | source.loopEnd = loopendControl.value; 61 | loopendValue.textContent = loopendControl.value; 62 | }); 63 | -------------------------------------------------------------------------------- /audio-buffer-source-node/playbackrate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web Audio API examples: setting playback rate 8 | 9 | 10 | 11 | 12 | 13 |

Web Audio API examples: setting playback rate

14 | 15 | 16 | 17 | 18 |

Set playback rate

19 | 27 | 1.0 × the original rate. 28 | 29 | 30 | -------------------------------------------------------------------------------- /audio-buffer-source-node/playbackrate/rnb-lofi-melody-loop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/audio-buffer-source-node/playbackrate/rnb-lofi-melody-loop.wav -------------------------------------------------------------------------------- /audio-buffer-source-node/playbackrate/script.js: -------------------------------------------------------------------------------- 1 | let audioCtx; 2 | let buffer; 3 | let source; 4 | 5 | const play = document.getElementById("play"); 6 | const stop = document.getElementById("stop"); 7 | 8 | const playbackControl = document.getElementById("playback-rate-control"); 9 | const playbackValue = document.getElementById("playback-rate-value"); 10 | 11 | async function loadAudio() { 12 | try { 13 | // Load an audio file 14 | const response = await fetch("rnb-lofi-melody-loop.wav"); 15 | // Decode it 16 | buffer = await audioCtx.decodeAudioData(await response.arrayBuffer()); 17 | } catch (err) { 18 | console.error(`Unable to fetch the audio file. Error: ${err.message}`); 19 | } 20 | } 21 | 22 | play.addEventListener("click", async () => { 23 | if (!audioCtx) { 24 | audioCtx = new AudioContext(); 25 | await loadAudio(); 26 | } 27 | source = audioCtx.createBufferSource(); 28 | source.buffer = buffer; 29 | source.connect(audioCtx.destination); 30 | source.loop = true; 31 | source.playbackRate.value = playbackControl.value; 32 | source.start(); 33 | play.disabled = true; 34 | stop.disabled = false; 35 | playbackControl.disabled = false; 36 | }); 37 | 38 | stop.addEventListener("click", () => { 39 | source.stop(); 40 | play.disabled = false; 41 | stop.disabled = true; 42 | playbackControl.disabled = true; 43 | }); 44 | 45 | playbackControl.oninput = () => { 46 | source.playbackRate.value = playbackControl.value; 47 | playbackValue.textContent = playbackControl.value; 48 | }; 49 | -------------------------------------------------------------------------------- /audio-buffer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Web Audio API: AudioBuffer 7 | 8 | 9 | 10 |

Web Audio API: AudioBuffer

11 | 12 | 13 | 68 | 69 | -------------------------------------------------------------------------------- /audio-param/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web Audio API examples: AudioParam 8 | 9 | 10 | 11 |

Web Audio API examples: AudioParam

12 | 17 |
18 | 21 |
24 | 27 |
30 | 33 |
36 | 37 |
38 | 39 |
40 | 41 | 132 | 133 | -------------------------------------------------------------------------------- /audio-param/viper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/audio-param/viper.mp3 -------------------------------------------------------------------------------- /audio-param/viper.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/audio-param/viper.ogg -------------------------------------------------------------------------------- /audiocontext-states/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web Audio API examples: AudioContext object's states 8 | 9 | 10 | 11 |

Web Audio API examples: AudioContext object's states

12 | 13 | 14 | 15 | 16 | 17 |

Current context time: No context exists.

18 | 19 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /audioworklet/hiss-generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds hiss into each channel. 3 | * 4 | * @class HissGeneratorProcessor 5 | * @extends AudioWorkletProcessor 6 | **/ 7 | 8 | class HissGeneratorProcessor extends AudioWorkletProcessor { 9 | constructor() { 10 | super(); 11 | } 12 | 13 | static get parameterDescriptors() { 14 | return [ 15 | { 16 | name: "gain", 17 | defaultValue: 0.2, 18 | minValue: 0, 19 | maxValue: 1, 20 | }, 21 | ]; 22 | } 23 | 24 | /** 25 | * Called by the browser's audio subsystem with 26 | * packets of audio data to be processed. 27 | * 28 | * @param[in] inputList Array of inputs 29 | * @param[in] outputList Array of outputs 30 | * @param[in] parameters Parameters object 31 | * 32 | * `inputList` and `outputList` are each arrays of inputs 33 | * or outputs, each of which is in turn an array of `Float32Array`s, 34 | * each of which contains the audio data for one channel (left/right/etc) 35 | * for the current sample packet. 36 | * 37 | * `parameters` is an object containing the `AudioParam` values 38 | * for the current block of audio data. 39 | **/ 40 | 41 | process(inputList, outputList, parameters) { 42 | const gain = parameters.gain[0]; 43 | const sourceLimit = Math.min(inputList.length, outputList.length); 44 | 45 | for (let inputNum = 0; inputNum < sourceLimit; inputNum++) { 46 | let input = inputList[inputNum]; 47 | let output = outputList[inputNum]; 48 | let channelCount = Math.min(input.length, output.length); 49 | 50 | // The input list and output list are each arrays of 51 | // Float32Array objects, each of which contains the 52 | // samples for one channel. 53 | 54 | for (let channel = 0; channel < channelCount; channel++) { 55 | let sampleCount = input[channel].length; 56 | 57 | for (let i = 0; i < sampleCount; i++) { 58 | let sample = input[channel][i]; 59 | let rnd = 2 * (Math.random() - 0.5); // Range: -1 to 1 60 | 61 | sample = sample + rnd * gain; 62 | 63 | // Prevent clipping 64 | 65 | if (sample > 1.0) { 66 | sample = 1.0; 67 | } else if (sample < -1.0) { 68 | sample = -1.0; 69 | } 70 | 71 | output[channel][i] = sample; 72 | } 73 | } 74 | } 75 | 76 | // Return; let the system know we're still active 77 | // and ready to process audio. 78 | 79 | return true; 80 | } 81 | } 82 | 83 | registerProcessor("hiss-generator", HissGeneratorProcessor); 84 | -------------------------------------------------------------------------------- /audioworklet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AudioWorklet Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

AudioWorklet Demo

17 | 18 |

19 | This page is a demo for the AudioWorklet API, which lets you 20 | process audio in the background using a worklet. 21 |

22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 | 39 |
40 | 41 |
42 | 43 | 44 | 45 | 54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /audioworklet/script.js: -------------------------------------------------------------------------------- 1 | let audioContext = null; 2 | let hissGainRange; 3 | let oscGainRange; 4 | let hissGenNode; 5 | let gainNode; 6 | let hissGainParam; 7 | 8 | async function createHissProcessor() { 9 | if (!audioContext) { 10 | try { 11 | audioContext = new AudioContext(); 12 | } catch (e) { 13 | console.log("** Error: Unable to create audio context"); 14 | return null; 15 | } 16 | } 17 | 18 | let processorNode; 19 | 20 | try { 21 | processorNode = new AudioWorkletNode(audioContext, "hiss-generator"); 22 | } catch (e) { 23 | try { 24 | console.log("adding..."); 25 | await audioContext.audioWorklet.addModule("hiss-generator.js"); 26 | processorNode = new AudioWorkletNode(audioContext, "hiss-generator"); 27 | } catch (e) { 28 | console.log(`** Error: Unable to create worklet node: ${e}`); 29 | return null; 30 | } 31 | } 32 | 33 | await audioContext.resume(); 34 | return processorNode; 35 | } 36 | 37 | async function audioDemoStart() { 38 | hissGenNode = await createHissProcessor(); 39 | if (!hissGenNode) { 40 | console.log("** Error: unable to create hiss processor"); 41 | return; 42 | } 43 | const soundSource = new OscillatorNode(audioContext); 44 | gainNode = audioContext.createGain(); 45 | 46 | // Configure the oscillator node 47 | 48 | soundSource.type = "square"; 49 | soundSource.frequency.setValueAtTime(440, audioContext.currentTime); // (A4) 50 | 51 | // Configure the gain for the oscillator 52 | 53 | gainNode.gain.setValueAtTime(oscGainRange.value, audioContext.currentTime); 54 | 55 | // Connect and start 56 | 57 | soundSource 58 | .connect(gainNode) 59 | .connect(hissGenNode) 60 | .connect(audioContext.destination); 61 | soundSource.start(); 62 | 63 | // Get access to the worklet's gain parameter 64 | 65 | hissGainParam = hissGenNode.parameters.get("gain"); 66 | hissGainParam.setValueAtTime(hissGainRange.value, audioContext.currentTime); 67 | } 68 | 69 | window.addEventListener("load", (event) => { 70 | document.getElementById("toggle").addEventListener("click", toggleSound); 71 | 72 | hissGainRange = document.getElementById("hiss-gain"); 73 | oscGainRange = document.getElementById("osc-gain"); 74 | 75 | hissGainRange.oninput = updateHissGain; 76 | oscGainRange.oninput = updateOscGain; 77 | 78 | hissGainRange.disabled = true; 79 | oscGainRange.disabled = true; 80 | }); 81 | 82 | async function toggleSound(event) { 83 | if (!audioContext) { 84 | audioDemoStart(); 85 | 86 | hissGainRange.disabled = false; 87 | oscGainRange.disabled = false; 88 | } else { 89 | hissGainRange.disabled = true; 90 | oscGainRange.disabled = true; 91 | 92 | await audioContext.close(); 93 | audioContext = null; 94 | } 95 | } 96 | 97 | function updateHissGain(event) { 98 | hissGainParam.setValueAtTime(event.target.value, audioContext.currentTime); 99 | } 100 | 101 | function updateOscGain(event) { 102 | gainNode.gain.setValueAtTime(event.target.value, audioContext.currentTime); 103 | } 104 | -------------------------------------------------------------------------------- /audioworklet/style.css: -------------------------------------------------------------------------------- 1 | /* CSS files add styling rules to your content */ 2 | 3 | body { 4 | font-family: helvetica, arial, sans-serif; 5 | margin: 2em; 6 | } 7 | 8 | h1 { 9 | font-style: bold; 10 | color: #330000; 11 | } 12 | 13 | .control-box { 14 | margin-top: 1em; 15 | width: 20em; 16 | border: 2px solid #333; 17 | border-radius: 2em; 18 | padding: 1em; 19 | } 20 | 21 | .control { 22 | margin-bottom: 1em; 23 | } 24 | 25 | .control input { 26 | width: 14em; 27 | left: 20em; 28 | float: right; 29 | } 30 | -------------------------------------------------------------------------------- /compressor-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web Audio API: Compressor example 8 | 9 | 10 | 11 |

Web Audio API: Compressor example

12 | 17 | 18 | 19 | 69 | 70 | -------------------------------------------------------------------------------- /compressor-example/viper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/compressor-example/viper.mp3 -------------------------------------------------------------------------------- /compressor-example/viper.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/compressor-example/viper.ogg -------------------------------------------------------------------------------- /create-media-stream-destination/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web Audio API examples: MediaStreamAudioDestination() 6 | 7 | 8 |

Web Audio API examples: MediaStreamAudioDestination()

9 | 10 |

Encoding a pure sine wave to an Opus file

11 |

12 | 13 | 14 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /decode-audio-data/callback/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Web Audio API examples: decodeAudioData() with callback 7 | 8 | 9 | 10 | 11 |

Web Audio API examples: decodeAudioData() with callback

12 | 13 | 14 | 15 | 16 |

Set playback rate

17 | 25 | 1.0 26 | 27 |

Set loop start and loop end

28 | 36 | 0 37 | 38 | 46 | 0 47 | 48 |

49 | If the stop time is lower or equal to the start time, it'll loop over the 50 | whole track. 51 |

52 | 53 | 54 | -------------------------------------------------------------------------------- /decode-audio-data/callback/script.js: -------------------------------------------------------------------------------- 1 | let audioCtx; 2 | let buffer; 3 | let source; 4 | 5 | // Get UI elements 6 | const play = document.getElementById("play"); 7 | const stop = document.getElementById("stop"); 8 | 9 | const playbackControl = document.getElementById("playback-rate-control"); 10 | const playbackValue = document.getElementById("playback-rate-value"); 11 | 12 | const loopstartControl = document.getElementById("loopstart-control"); 13 | const loopstartValue = document.getElementById("loopstart-value"); 14 | 15 | const loopendControl = document.getElementById("loopend-control"); 16 | const loopendValue = document.getElementById("loopend-value"); 17 | 18 | function playBuffer() { 19 | source = audioCtx.createBufferSource(); 20 | source.buffer = buffer; 21 | source.playbackRate.value = playbackControl.value; 22 | source.connect(audioCtx.destination); 23 | source.loop = true; 24 | source.loopStart = loopstartControl.value; 25 | source.loopEnd = loopendControl.value; 26 | source.start(); 27 | play.disabled = true; 28 | stop.disabled = false; 29 | playbackControl.disabled = false; 30 | loopstartControl.disabled = false; 31 | loopendControl.disabled = false; 32 | } 33 | 34 | async function loadAudio() { 35 | try { 36 | const response = await fetch("viper.mp3"); 37 | audioCtx.decodeAudioData(await response.arrayBuffer(), (buf) => { 38 | // executes when buffer has been decoded 39 | buffer = buf; 40 | const max = Math.floor(buf.duration); 41 | loopstartControl.max = max; 42 | loopendControl.max = max; 43 | play.disabled = false; // buffer loaded, enable play button 44 | playBuffer(); 45 | }); 46 | } catch (err) { 47 | console.error(`Unable to fetch the audio file. Error: ${err.message}`); 48 | } 49 | } 50 | 51 | play.addEventListener("click", async () => { 52 | if (!audioCtx) { 53 | audioCtx = new AudioContext(); 54 | await loadAudio(); 55 | } else { 56 | playBuffer(); 57 | } 58 | }); 59 | 60 | stop.addEventListener("click", () => { 61 | source.stop(); 62 | play.disabled = false; 63 | playbackControl.disabled = true; 64 | loopstartControl.disabled = true; 65 | loopendControl.disabled = true; 66 | }); 67 | 68 | playbackControl.addEventListener("input", () => { 69 | source.playbackRate.value = playbackControl.value; 70 | playbackValue.textContent = playbackControl.value; 71 | }); 72 | 73 | loopstartControl.addEventListener("input", () => { 74 | source.loopStart = loopstartControl.value; 75 | loopstartValue.textContent = loopstartControl.value; 76 | }); 77 | 78 | loopendControl.oninput = () => { 79 | source.loopEnd = loopendControl.value; 80 | loopendValue.textContent = loopendControl.value; 81 | }; 82 | -------------------------------------------------------------------------------- /decode-audio-data/callback/viper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/decode-audio-data/callback/viper.mp3 -------------------------------------------------------------------------------- /decode-audio-data/callback/viper.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/decode-audio-data/callback/viper.ogg -------------------------------------------------------------------------------- /decode-audio-data/promise/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Web Audio API examples: decodeAudioData() with Promise 7 | 8 | 9 | 10 | 11 |

Web Audio API examples: decodeAudioData() with Promise

12 | 13 | 14 | 15 | 16 |

Set playback rate

17 | 25 | 1.0 26 | 27 |

Set loop start and loop end

28 | 36 | 0 37 | 38 | 46 | 0 47 | 48 |

49 | If the stop time is lower or equal to the start time, it'll loop over the 50 | whole track. 51 |

52 | 53 | 54 | -------------------------------------------------------------------------------- /decode-audio-data/promise/script.js: -------------------------------------------------------------------------------- 1 | // define variables 2 | let audioCtx; 3 | let buffer; 4 | let source; 5 | 6 | const play = document.getElementById("play"); 7 | const stop = document.getElementById("stop"); 8 | 9 | const playbackControl = document.getElementById("playback-rate-control"); 10 | const playbackValue = document.getElementById("playback-rate-value"); 11 | 12 | const loopstartControl = document.getElementById("loopstart-control"); 13 | const loopstartValue = document.getElementById("loopstart-value"); 14 | 15 | const loopendControl = document.getElementById("loopend-control"); 16 | const loopendValue = document.getElementById("loopend-value"); 17 | 18 | async function loadAudio() { 19 | try { 20 | // Load an audio file 21 | const response = await fetch("viper.mp3"); 22 | // Decode it 23 | buffer = await audioCtx.decodeAudioData(await response.arrayBuffer()); 24 | const max = Math.floor(buffer.duration); 25 | loopstartControl.setAttribute("max", max); 26 | loopendControl.setAttribute("max", max); 27 | } catch (err) { 28 | console.error(`Unable to fetch the audio file. Error: ${err.message}`); 29 | } 30 | } 31 | 32 | play.addEventListener("click", async () => { 33 | if (!audioCtx) { 34 | audioCtx = new AudioContext(); 35 | await loadAudio(); 36 | } 37 | // Create a new single-use AudioBufferSourceNode 38 | source = audioCtx.createBufferSource(); 39 | source.buffer = buffer; 40 | source.playbackRate.value = playbackControl.value; 41 | source.connect(audioCtx.destination); 42 | source.loop = true; 43 | source.loopStart = loopstartControl.value; 44 | source.loopEnd = loopendControl.value; 45 | source.start(); 46 | play.disabled = true; 47 | stop.disabled = false; 48 | playbackControl.disabled = false; 49 | loopstartControl.disabled = false; 50 | loopendControl.disabled = false; 51 | }); 52 | 53 | stop.addEventListener("click", () => { 54 | source.stop(); 55 | play.disabled = false; 56 | playbackControl.disabled = true; 57 | loopstartControl.disabled = true; 58 | loopendControl.disabled = true; 59 | }); 60 | 61 | playbackControl.addEventListener("input", () => { 62 | source.playbackRate.value = playbackControl.value; 63 | playbackValue.textContent = playbackControl.value; 64 | }); 65 | 66 | loopstartControl.addEventListener("input", () => { 67 | source.loopStart = loopstartControl.value; 68 | loopstartValue.textContent = loopstartControl.value; 69 | }); 70 | 71 | loopendControl.addEventListener("input", () => { 72 | source.loopEnd = loopendControl.value; 73 | loopendValue.textContent = loopendControl.value; 74 | }); 75 | -------------------------------------------------------------------------------- /decode-audio-data/promise/viper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/decode-audio-data/promise/viper.mp3 -------------------------------------------------------------------------------- /iirfilter-node/outfoxing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/iirfilter-node/outfoxing.mp3 -------------------------------------------------------------------------------- /media-source-buffer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web Audio API examples: MediaElementAudioSource() 8 | 9 | 10 | 11 |

Web Audio API examples: MediaElementAudioSource()

12 |

Position the cursor vertically to change the volume (down is louder).

13 | 18 | 19 | 60 | 61 | -------------------------------------------------------------------------------- /media-source-buffer/viper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/media-source-buffer/viper.mp3 -------------------------------------------------------------------------------- /media-source-buffer/viper.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/media-source-buffer/viper.ogg -------------------------------------------------------------------------------- /multi-track/bassguitar.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/multi-track/bassguitar.mp3 -------------------------------------------------------------------------------- /multi-track/clav.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/multi-track/clav.mp3 -------------------------------------------------------------------------------- /multi-track/drums.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/multi-track/drums.mp3 -------------------------------------------------------------------------------- /multi-track/horns.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/multi-track/horns.mp3 -------------------------------------------------------------------------------- /multi-track/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web Audio API examples: Loading audio files 6 | 10 | 14 | 15 | 16 | 17 | 23 | 24 | 25 |
26 |
27 |
    28 |
  • 29 | Lead Guitar 30 |

    Loading...

    31 | 38 |
  • 39 |
  • 40 | Bass Guitar 41 |

    Loading...

    42 | 49 |
  • 50 |
  • 51 | Drums 52 |

    Loading...

    53 | 60 |
  • 61 |
  • 62 | Horns 63 |

    Loading...

    64 | 71 |
  • 72 |
  • 73 | Clavi 74 |

    Loading...

    75 | 82 |
  • 83 |
84 |

85 | All tracks sourced from jplayer.org 86 |

87 |
88 |
89 | 90 | 91 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /multi-track/leadguitar.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/multi-track/leadguitar.mp3 -------------------------------------------------------------------------------- /multi-track/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --orange: hsla(32, 100%, 50%, 1); 3 | --yellow: hsla(49, 99%, 50%, 1); 4 | --lime: hsla(82, 90%, 45%, 1); 5 | --green: hsla(127, 81%, 41%, 1); 6 | --red: hsla(342, 93%, 53%, 1); 7 | --pink: hsla(314, 85%, 45%, 1); 8 | --blue: hsla(211, 92%, 52%, 1); 9 | --purple: hsla(283, 92%, 44%, 1); 10 | --cyan: hsla(195, 98%, 55%, 1); 11 | --white: hsla(0, 0%, 95%, 1); 12 | --black: hsla(0, 0%, 10%, 1); 13 | 14 | /* abstract our colours */ 15 | --boxMain: var(--cyan); 16 | --boxSecond: var(--blue); 17 | --boxHigh: var(--yellow); 18 | --border: 1vmin solid var(--black); 19 | --borderRad: 2px; 20 | } 21 | 22 | * { 23 | box-sizing: border-box; 24 | } 25 | 26 | body { 27 | background-color: var(--white); 28 | padding: 4vmax; 29 | font-family: sans-serif, system-ui; 30 | font-size: 120%; 31 | color: var(--black); 32 | } 33 | 34 | #startbutton { 35 | width: 12vw; 36 | line-height: 3; 37 | background-color: var(--boxMain); 38 | border: var(--border); 39 | border-radius: var(--borderRad); 40 | position: absolute; 41 | top: 1px; 42 | left: 1px; 43 | } 44 | 45 | #startbutton:hover { 46 | background-color: var(--boxSecond); 47 | } 48 | 49 | .wrapper { 50 | display: flex; 51 | justify-content: center; 52 | align-items: center; 53 | } 54 | 55 | #tracks { 56 | } 57 | #tracks ul { 58 | list-style: none; 59 | margin: 0px; 60 | padding: 0px; 61 | width: 62vw; 62 | background-color: var(--boxMain); 63 | border: var(--border); 64 | border-radius: var(--borderRad); 65 | } 66 | #tracks li { 67 | display: flex; 68 | justify-content: space-between; 69 | align-items: center; 70 | padding: 2vw; 71 | border-bottom: var(--border); 72 | } 73 | #tracks li:last-child { 74 | border-bottom: none; 75 | } 76 | #tracks li[data-loading="true"] { 77 | background-color: var(--boxHigh); 78 | } 79 | 80 | .loading-text, 81 | .track, 82 | .playbutton { 83 | } 84 | 85 | .loading-text { 86 | } 87 | .track { 88 | color: var(--black); 89 | } 90 | .track:hover { 91 | text-decoration: none; 92 | } 93 | 94 | .playbutton { 95 | display: none; 96 | padding: 30px; 97 | border: var(--border); 98 | border-radius: var(--borderRad); 99 | font-size: 100%; 100 | cursor: pointer; 101 | } 102 | .playbutton span { 103 | display: none; 104 | } 105 | [data-playing] { 106 | background: var(--red) 107 | url('data:image/svg+xml;charset=utf8,') 108 | no-repeat center center; 109 | background-size: 60% 60%; 110 | } 111 | [data-playing]:hover, 112 | [data-playing="true"] { 113 | background-color: var(--green); 114 | } 115 | 116 | .sourced { 117 | font-size: 86%; 118 | text-align: right; 119 | } 120 | -------------------------------------------------------------------------------- /offline-audio-context-promise/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Web Audio API examples: OfflineAudioContext (using promises) 7 | 8 | 9 | 10 | 11 |

Web Audio API examples: OfflineAudioContext (using promises)

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /offline-audio-context-promise/script.js: -------------------------------------------------------------------------------- 1 | // Define both online and offline audio contexts 2 | let audioCtx; // Must be initialized after a user interaction 3 | const offlineCtx = new OfflineAudioContext(2, 44100 * 40, 44100); 4 | 5 | // Define constants for dom nodes 6 | const play = document.querySelector("#play"); 7 | 8 | function getData() { 9 | // Fetch an audio track, decode it and stick it in a buffer. 10 | // Then we put the buffer into the source and can play it. 11 | fetch("viper.ogg") 12 | .then((response) => response.arrayBuffer()) 13 | .then((downloadedBuffer) => audioCtx.decodeAudioData(downloadedBuffer)) 14 | .then((decodedBuffer) => { 15 | console.log("File downloaded.successfully."); 16 | const source = new AudioBufferSourceNode(offlineCtx, { 17 | buffer: decodedBuffer, 18 | }); 19 | source.connect(offlineCtx.destination); 20 | return source.start(); 21 | }) 22 | .then(() => offlineCtx.startRendering()) 23 | .then((renderedBuffer) => { 24 | console.log("Rendering completed successfully."); 25 | play.disabled = false; 26 | const song = new AudioBufferSourceNode(audioCtx, { 27 | buffer: renderedBuffer, 28 | }); 29 | song.connect(audioCtx.destination); 30 | 31 | // Start the song 32 | song.start(); 33 | }) 34 | .catch((err) => { 35 | console.error(`Error encountered: ${err}`); 36 | }); 37 | } 38 | 39 | // Activate the play button 40 | play.onclick = () => { 41 | play.disabled = true; 42 | // We can initialize the context as the user clicked. 43 | audioCtx = new AudioContext(); 44 | 45 | // Fetch the data and start the song 46 | getData(); 47 | }; 48 | -------------------------------------------------------------------------------- /offline-audio-context-promise/viper.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/offline-audio-context-promise/viper.ogg -------------------------------------------------------------------------------- /offline-audio-context/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web Audio API examples: OfflineAudioContext 8 | 9 | 10 | 11 |

Web Audio API examples: OfflineAudioContext

12 | 13 | 14 | 68 | 69 | -------------------------------------------------------------------------------- /offline-audio-context/viper.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/offline-audio-context/viper.ogg -------------------------------------------------------------------------------- /output-timestamp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Web Audio API examples: Output timestamp 7 | 8 | 9 | 10 |

Web Audio API examples: Output timestamp

11 | 12 | 13 | 14 | 15 | 74 | 75 | -------------------------------------------------------------------------------- /output-timestamp/outfoxing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/output-timestamp/outfoxing.mp3 -------------------------------------------------------------------------------- /panner-node/icons/boom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /panner-node/icons/left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /panner-node/icons/right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /panner-node/icons/zoom-in.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /panner-node/icons/zoom-out.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /panner-node/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Room of metal 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 |

Room of metal

20 | 21 | 22 | 23 | 24 |

Listener data:

25 |

Panner data:

26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 | 34 | 35 | 36 | 37 |
38 | 39 |

40 | Instructions: After pressing Play, use the left, right, 41 | zoom in and zoom out buttons to control your position relative to the boom 42 | box. 43 |

44 |

45 | Run this on a computer with stereo sound to see how this affects the 47 | sound spatialisation. 49 |

50 | 51 |

52 | Boom box icons courtesy of 53 | flatiron.com, and created by 57 | Freepik, 58 | Appzgear and 59 | Adam Whitcroft 62 |

63 | 64 | 65 | -------------------------------------------------------------------------------- /panner-node/main.js: -------------------------------------------------------------------------------- 1 | // set up listener and panner position information 2 | let WIDTH = window.innerWidth; 3 | let HEIGHT = window.innerHeight; 4 | 5 | let xPos = Math.floor(WIDTH / 2); 6 | let yPos = Math.floor(HEIGHT / 2); 7 | let zPos = 295; 8 | 9 | // play, stop, and other important dom nodes 10 | 11 | const playBtn = document.querySelector(".play"); 12 | const stopBtn = document.querySelector(".stop"); 13 | const boomBox = document.querySelector(".boom-box"); 14 | const listenerData = document.querySelector(".listener-data"); 15 | const pannerData = document.querySelector(".panner-data"); 16 | const pulseWrapper = document.querySelector(".pulse-wrapper"); 17 | 18 | // movement controls and initial data 19 | 20 | const leftButton = document.querySelector(".left"); 21 | const rightButton = document.querySelector(".right"); 22 | const zoomInButton = document.querySelector(".zoom-in"); 23 | const zoomOutButton = document.querySelector(".zoom-out"); 24 | 25 | let boomX = 0; 26 | let boomY = 0; 27 | let boomZoom = 0.5; 28 | 29 | // Variables to hold information that needs to be assigned upon play 30 | // Audio context must be created only after the user has interacted. 31 | let audioCtx; 32 | let panner; 33 | let listener; 34 | let source; 35 | 36 | function init() { 37 | audioCtx = new AudioContext(); 38 | listener = audioCtx.listener; 39 | console.log(listener); 40 | 41 | panner = new PannerNode(audioCtx, { 42 | panningModel: "HRTF", 43 | distanceModel: "inverse", 44 | refDistance: 1, 45 | maxDistance: 10000, 46 | rolloffFactor: 1, 47 | coneInnerAngle: 360, 48 | coneOuterAngle: 0, 49 | coneOuterGain: 0, 50 | orientationX: 1, 51 | orientationY: 0, 52 | orientationZ: 0, 53 | }); 54 | 55 | if (!listener.forwardX) { 56 | // Deprecated but still needed (July 2022) 57 | listener.setOrientation(0, 0, -1, 0, 1, 0); 58 | } else { 59 | // Standard way 60 | listener.forwardX.value = 0; 61 | listener.forwardY.value = 0; 62 | listener.forwardZ.value = -1; 63 | listener.upX.value = 0; 64 | listener.upY.value = 1; 65 | listener.upZ.value = 0; 66 | } 67 | 68 | leftBound = -xPos + 50; 69 | rightBound = xPos - 50; 70 | 71 | xIterator = WIDTH / 150; 72 | 73 | // listener will always be in the same place for this demo 74 | 75 | if (!listener.positionX) { 76 | // Deprecated but still needed (July 2022) 77 | listener.setPosition(xPos, yPos, 300); 78 | } else { 79 | // Standard way 80 | listener.positionX.value = xPos; 81 | listener.positionY.value = yPos; 82 | listener.positionZ.value = 300; 83 | } 84 | 85 | listenerData.textContent = `Listener data: X ${xPos} Y ${yPos} Z ${300}`; 86 | 87 | // panner will move as the boombox graphic moves around on the screen 88 | function positionPanner() { 89 | panner.positionX.value = xPos; 90 | panner.positionY.value = yPos; 91 | panner.positionZ.value = zPos; 92 | pannerData.textContent = `Panner data: X ${xPos} Y ${yPos} Z ${300}`; 93 | } 94 | 95 | // Fetch an audio track, decode it and stick it in a buffer. 96 | // Then we put the buffer into the source and start it 97 | function getData() { 98 | fetch("viper.ogg") 99 | .then((response) => response.arrayBuffer()) 100 | .then((downloadedBuffer) => audioCtx.decodeAudioData(downloadedBuffer)) 101 | .then((decodedBuffer) => { 102 | source = new AudioBufferSourceNode(audioCtx, { 103 | buffer: decodedBuffer, 104 | }); 105 | source.connect(panner); 106 | panner.connect(audioCtx.destination); 107 | positionPanner(); 108 | source.loop = true; 109 | console.log("Loaded!"); 110 | source.start(0); 111 | }) 112 | .catch((e) => { 113 | console.error(`Error while preparing the audio data ${e.err}`); 114 | }); 115 | } 116 | 117 | getData(); 118 | 119 | // controls to move left and right past the boom box 120 | // and zoom in and out 121 | function moveRight() { 122 | boomX += -xIterator; 123 | xPos += -0.066; 124 | 125 | if (boomX <= leftBound) { 126 | boomX = leftBound; 127 | xPos = WIDTH / 2 - 5; 128 | } 129 | 130 | boomBox.style.transform = `translate(${boomX}px, ${boomY}px) scale(${boomZoom})`; 131 | positionPanner(); 132 | rightLoop = requestAnimationFrame(moveRight); 133 | return rightLoop; 134 | } 135 | 136 | function moveLeft() { 137 | boomX += xIterator; 138 | xPos += 0.066; 139 | 140 | if (boomX > rightBound) { 141 | boomX = rightBound; 142 | xPos = WIDTH / 2 + 5; 143 | } 144 | 145 | positionPanner(); 146 | boomBox.style.transform = `translate(${boomX}px, ${boomY}px) scale(${boomZoom})`; 147 | leftLoop = requestAnimationFrame(moveLeft); 148 | return leftLoop; 149 | } 150 | 151 | function zoomIn() { 152 | boomZoom += 0.05; 153 | zPos += 0.066; 154 | 155 | if (boomZoom > 4) { 156 | boomZoom = 4; 157 | zPos = 299.9; 158 | } 159 | 160 | positionPanner(); 161 | boomBox.style.transform = `translate(${boomX}px, ${boomY}px) scale(${boomZoom})`; 162 | zoomInLoop = requestAnimationFrame(zoomIn); 163 | return zoomInLoop; 164 | } 165 | 166 | function zoomOut() { 167 | boomZoom += -0.05; 168 | zPos += -0.066; 169 | 170 | if (boomZoom <= 0.5) { 171 | boomZoom = 0.5; 172 | zPos = 295; 173 | } 174 | 175 | positionPanner(); 176 | boomBox.style.transform = 177 | boomBox.style.transform = `translate(${boomX}px, ${boomY}px) scale(${boomZoom})`; 178 | zoomOutLoop = requestAnimationFrame(zoomOut); 179 | return zoomOutLoop; 180 | } 181 | 182 | // In each of the cases below, onmousedown runs the functions above 183 | // onmouseup cancels the resulting requestAnimationFrames. 184 | leftButton.onmousedown = moveLeft; 185 | leftButton.onmouseup = () => { 186 | cancelAnimationFrame(leftLoop); 187 | }; 188 | 189 | rightButton.onmousedown = moveRight; 190 | rightButton.onmouseup = () => { 191 | cancelAnimationFrame(rightLoop); 192 | }; 193 | 194 | zoomInButton.onmousedown = zoomIn; 195 | zoomInButton.onmouseup = () => { 196 | window.cancelAnimationFrame(zoomInLoop); 197 | }; 198 | 199 | zoomOutButton.onmousedown = zoomOut; 200 | zoomOutButton.onmouseup = () => { 201 | window.cancelAnimationFrame(zoomOutLoop); 202 | }; 203 | } 204 | 205 | // Wire up buttons to stop and play audio 206 | stopBtn.disabled = true; 207 | 208 | playBtn.onclick = () => { 209 | init(); 210 | playBtn.disabled = true; 211 | stopBtn.disabled = false; 212 | pulseWrapper.classList.add("pulsate"); 213 | }; 214 | 215 | stopBtn.onclick = () => { 216 | source.stop(0); 217 | playBtn.disabled = false; 218 | stopBtn.disabled = true; 219 | pulseWrapper.classList.remove("pulsate"); 220 | }; 221 | -------------------------------------------------------------------------------- /panner-node/style.css: -------------------------------------------------------------------------------- 1 | /* basic page setup and text stuff */ 2 | 3 | body, 4 | html { 5 | margin: 0; 6 | font-size: 10px; 7 | } 8 | 9 | body { 10 | min-width: 600px; 11 | } 12 | 13 | h1 { 14 | font-family: "UnifrakturMaguntia", cursive; 15 | text-align: center; 16 | font-size: 8rem; 17 | margin: 4rem 0 0 0; 18 | } 19 | 20 | p { 21 | text-align: center; 22 | font-size: 1.4rem; 23 | } 24 | 25 | /* button styling and positioning */ 26 | 27 | button { 28 | background-color: #0088cc; 29 | background-image: linear-gradient(to bottom, #0088cc 0%, #0055cc 100%); 30 | text-shadow: 1px 1px 1px black; 31 | text-align: center; 32 | color: white; 33 | border: none; 34 | width: 120px; 35 | font-size: 1.5rem; 36 | line-height: 1.5rem; 37 | padding: 0.5rem; 38 | 39 | position: absolute; 40 | 41 | box-shadow: inset 0px 0px 0px rgba(0, 0, 0, 0.7); 42 | transition: 0.1s all; 43 | -webkit-transition: 0.1s all; 44 | } 45 | 46 | button:hover, 47 | button:focus { 48 | opacity: 0.8; 49 | } 50 | 51 | button:active { 52 | box-shadow: inset 0px 0px 10px rgba(0, 0, 0, 0.7); 53 | } 54 | 55 | .play { 56 | top: 2px; 57 | left: 2px; 58 | } 59 | 60 | .stop { 61 | top: 2px; 62 | left: 124px; 63 | } 64 | 65 | .left { 66 | top: 44px; 67 | right: 64px; 68 | background: url(icons/left.svg) no-repeat center, 69 | linear-gradient(to bottom, #0088cc 0%, #0055cc 100%); 70 | } 71 | 72 | .right { 73 | top: 44px; 74 | right: 2px; 75 | background: url(icons/right.svg) no-repeat center, 76 | linear-gradient(to bottom, #0088cc 0%, #0055cc 100%); 77 | } 78 | 79 | .zoom-in { 80 | top: 2px; 81 | right: 31px; 82 | background: url(icons/zoom-in.svg) no-repeat center, 83 | linear-gradient(to bottom, #0088cc 0%, #0055cc 100%); 84 | } 85 | 86 | .zoom-out { 87 | top: 86px; 88 | right: 31px; 89 | background: url(icons/zoom-out.svg) no-repeat center, 90 | linear-gradient(to bottom, #0088cc 0%, #0055cc 100%); 91 | } 92 | 93 | .move { 94 | width: 60px; 95 | height: 40px; 96 | background-size: 50% 60%, 100% 100%; 97 | } 98 | 99 | /* setting up the room of metal */ 100 | 101 | .room { 102 | border-top: 1px solid black; 103 | border-bottom: 1px solid black; 104 | height: 300px; 105 | width: 100%; 106 | position: relative; 107 | } 108 | 109 | .flex-wrapper { 110 | width: inherit; 111 | height: inherit; 112 | 113 | display: flex; 114 | align-items: center; 115 | justify-content: center; 116 | 117 | overflow: hidden; 118 | } 119 | 120 | .boom-box { 121 | width: 200px; 122 | height: 180px; 123 | background: url(icons/boom.svg) no-repeat; 124 | background-size: 100% 100%; 125 | 126 | -webkit-transform: scale(0.5); 127 | transform: scale(0.5); 128 | } 129 | 130 | .pulse-wrapper { 131 | -webkit-transform-origin: center; 132 | transform-origin: center; 133 | } 134 | 135 | .pulsate { 136 | -webkit-animation: pulse 0.5s linear infinite alternate; 137 | animation: pulse 0.5s linear infinite alternate; 138 | } 139 | 140 | /* animation class for boom box pulsating */ 141 | 142 | @-webkit-keyframes pulse { 143 | from { 144 | -webkit-transform: scaleY(1); 145 | } 146 | to { 147 | -webkit-transform: scaleY(1.07); 148 | } 149 | } 150 | 151 | @keyframes pulse { 152 | from { 153 | transform: scaleY(1); 154 | } 155 | to { 156 | transform: scaleY(1.07); 157 | } 158 | } 159 | 160 | /* position data outputs */ 161 | 162 | .listener-data, 163 | .panner-data { 164 | width: 230px; 165 | position: absolute; 166 | right: 2px; 167 | } 168 | 169 | .listener-data { 170 | top: 2px; 171 | } 172 | 173 | .panner-data { 174 | top: 30px; 175 | } 176 | -------------------------------------------------------------------------------- /panner-node/viper.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/panner-node/viper.ogg -------------------------------------------------------------------------------- /script-processor-node/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Script processor node example 8 | 9 | 10 | 11 | 12 | 13 |

ScriptProcessorNode example

14 | 15 | 16 | 17 |

18 |   
19 | 
20 | 


--------------------------------------------------------------------------------
/script-processor-node/script.js:
--------------------------------------------------------------------------------
 1 | const myScript = document.querySelector("script");
 2 | const myPre = document.querySelector("pre");
 3 | const playButton = document.querySelector("button");
 4 | 
 5 | // Create AudioContext and buffer source
 6 | let audioCtx;
 7 | 
 8 | async function init() {
 9 |   audioCtx = new AudioContext();
10 |   const source = audioCtx.createBufferSource();
11 | 
12 |   // Create a ScriptProcessorNode with a bufferSize of 4096 and a single input and output channel
13 |   const scriptNode = audioCtx.createScriptProcessor(4096, 1, 1);
14 | 
15 |   // Load in an audio track using fetch() and decodeAudioData()
16 |   try {
17 |     const response = await fetch("viper.ogg");
18 |     const arrayBuffer = await response.arrayBuffer();
19 |     source.buffer = await audioCtx.decodeAudioData(arrayBuffer);
20 |   } catch (err) {
21 |     console.error(
22 |       `Unable to fetch the audio file: ${name} Error: ${err.message}`
23 |     );
24 |   }
25 | 
26 |   // Give the node a function to process audio events
27 |   scriptNode.addEventListener("audioprocess", (audioProcessingEvent) => {
28 |     // The input buffer is the song we loaded earlier
29 |     let inputBuffer = audioProcessingEvent.inputBuffer;
30 | 
31 |     // The output buffer contains the samples that will be modified and played
32 |     let outputBuffer = audioProcessingEvent.outputBuffer;
33 | 
34 |     // Loop through the output channels (in this case there is only one)
35 |     for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
36 |       let inputData = inputBuffer.getChannelData(channel);
37 |       let outputData = outputBuffer.getChannelData(channel);
38 | 
39 |       // Loop through the 4096 samples
40 |       for (let sample = 0; sample < inputBuffer.length; sample++) {
41 |         // make output equal to the same as the input
42 |         outputData[sample] = inputData[sample];
43 | 
44 |         // add noise to each output sample
45 |         outputData[sample] += (Math.random() * 2 - 1) * 0.1;
46 |       }
47 |     }
48 |   });
49 | 
50 |   source.connect(scriptNode);
51 |   scriptNode.connect(audioCtx.destination);
52 |   source.start();
53 | 
54 |   // When the buffer source stops playing, disconnect everything
55 |   source.addEventListener("ended", () => {
56 |     source.disconnect(scriptNode);
57 |     scriptNode.disconnect(audioCtx.destination);
58 |   });
59 | }
60 | 
61 | // wire up play button
62 | playButton.addEventListener("click", () => {
63 |   if (!audioCtx) {
64 |     init();
65 |   }
66 | });
67 | 


--------------------------------------------------------------------------------
/script-processor-node/viper.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/script-processor-node/viper.ogg


--------------------------------------------------------------------------------
/spatialization/outfoxing.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/spatialization/outfoxing.mp3


--------------------------------------------------------------------------------
/step-sequencer/dtmf.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/step-sequencer/dtmf.mp3


--------------------------------------------------------------------------------
/step-sequencer/style.css:
--------------------------------------------------------------------------------
  1 | :root {
  2 |   --orange: hsla(32, 100%, 50%, 1);
  3 |   --yellow: hsla(49, 99%, 50%, 1);
  4 |   --lime: hsla(82, 90%, 45%, 1);
  5 |   --green: hsla(127, 81%, 41%, 1);
  6 |   --red: hsla(342, 93%, 53%, 1);
  7 |   --pink: hsla(314, 85%, 45%, 1);
  8 |   --blue: hsla(211, 92%, 52%, 1);
  9 |   --purple: hsla(283, 92%, 44%, 1);
 10 |   --cyan: hsla(195, 98%, 55%, 1);
 11 |   --white: hsla(0, 0%, 95%, 1);
 12 |   --black: hsla(0, 0%, 10%, 1);
 13 | 
 14 |   /* abstract our colours */
 15 |   --boxMain: var(--pink);
 16 |   --boxSecond: var(--purple);
 17 |   --boxHigh: var(--yellow);
 18 |   --border: 1vmin solid var(--black);
 19 |   --borderRad: 2px;
 20 | }
 21 | 
 22 | * {
 23 |   box-sizing: border-box;
 24 | }
 25 | 
 26 | body {
 27 |   background-color: var(--white);
 28 |   padding: 4vmax;
 29 |   font-family: sans-serif, system-ui;
 30 |   font-size: 120%;
 31 |   color: var(--black);
 32 | }
 33 | 
 34 | h2 {
 35 |   font-size: 1.2em;
 36 | }
 37 | 
 38 | /* loading ~~~~~~~~~~~~~~~~~~~~~ */
 39 | .loading {
 40 |   background: var(--white);
 41 |   position: absolute;
 42 |   left: 0;
 43 |   right: 0;
 44 |   top: 0;
 45 |   bottom: 0;
 46 |   z-index: 2;
 47 |   display: flex;
 48 |   justify-content: center;
 49 |   align-items: center;
 50 | }
 51 | 
 52 | .loading p {
 53 |   font-size: 200%;
 54 |   text-align: center;
 55 |   animation: loading ease-in-out 2s infinite;
 56 | }
 57 | 
 58 | @keyframes loading {
 59 |   0% {
 60 |     opacity: 0;
 61 |   }
 62 | 
 63 |   50% {
 64 |     opacity: 1;
 65 |   }
 66 | 
 67 |   100% {
 68 |     opacity: 0;
 69 |   }
 70 | }
 71 | 
 72 | /* sequencer ~~~~~~~~~~~~~~~~~~~~~~~~~ */
 73 | #sequencer {
 74 |   width: 84vw;
 75 |   max-width: 900px;
 76 |   min-width: 600px;
 77 |   margin: 0 auto;
 78 |   background-color: var(--boxMain);
 79 |   border: var(--border);
 80 | }
 81 | 
 82 | /* ~~~~~~~~~~~~~~~~~~~~~~~ top section */
 83 | .controls-main {
 84 |   padding: 2vw;
 85 |   background-color: var(--boxSecond);
 86 |   border-bottom: var(--border);
 87 |   display: grid;
 88 |   grid-template-rows: auto;
 89 |   grid-template-columns: repeat(5, auto);
 90 |   align-items: center;
 91 | }
 92 | 
 93 | .controls-main label {
 94 |   justify-self: end;
 95 |   padding-right: 10px;
 96 | }
 97 | 
 98 | .controls-main span {
 99 |   padding-left: 10px;
100 | }
101 | 
102 | /* play button */
103 | #playBtn:checked {
104 |   align-self: stretch;
105 |   border: var(--border);
106 |   border-radius: var(--borderRad);
107 |   background-color: var(--boxSecond);
108 |   cursor: pointer;
109 | }
110 | 
111 | #playBtn {
112 |   appearance: none;
113 |   width: 9vw;
114 |   height: 6vw;
115 |   min-width: 36px;
116 |   min-height: 36px;
117 |   max-width: 112px;
118 |   max-height: 64x;
119 |   margin: 0;
120 |   padding: 0;
121 |   border: var(--border);
122 |   border-radius: var(--borderRad);
123 |   background: var(--pink)
124 |     url('data:image/svg+xml;charset=utf8,')
125 |     no-repeat center center;
126 |   background-size: 60% 60%;
127 |   cursor: pointer;
128 | }
129 | 
130 | #playBtn ~ label {
131 |   display: none;
132 | }
133 | 
134 | #playBtn:hover,
135 | #playBtn:checked:hover {
136 |   background-color: var(--yellow);
137 | }
138 | 
139 | #playBtn:checked {
140 |   background: var(--green)
141 |     url('data:image/svg+xml;charset=utf8,')
142 |     no-repeat center center;
143 |   background-size: 60% 60%;
144 | }
145 | 
146 | /* ~~~~~~~~~~~~~~~~~~~~~~~ main body */
147 | [class^="track"] {
148 |   display: grid;
149 |   grid-template-rows: auto;
150 |   grid-template-columns: 15% 35% 50%;
151 |   align-items: center;
152 |   padding: 2vmin;
153 | }
154 | 
155 | /* ~~~~~~~~~~~~~~~~~~~~~~~ sliders */
156 | .controls {
157 |   display: grid;
158 |   grid-template-rows: repeat(2, auto);
159 |   grid-template-columns: 1fr 4fr;
160 |   align-items: center;
161 | }
162 | 
163 | .controls label {
164 |   justify-self: end;
165 |   padding-right: 10px;
166 | }
167 | 
168 | .controls input {
169 |   margin-right: 20px;
170 | }
171 | 
172 | .controls input:nth-of-type(2),
173 | .controls label:nth-of-type(2) {
174 |   margin-top: 20px;
175 | }
176 | 
177 | /* ~~~~~~~~~~~~~~~~~~~~~~~ pads */
178 | .pads {
179 |   display: flex;
180 |   justify-content: space-between;
181 | }
182 | 
183 | .pads input {
184 |   appearance: none;
185 |   width: 9vw;
186 |   height: 9vw;
187 |   min-width: 56px;
188 |   min-height: 56px;
189 |   max-width: 96px;
190 |   max-height: 96px;
191 |   margin: 0;
192 |   padding: 0;
193 |   background-color: var(--white);
194 |   border: var(--border);
195 | }
196 | 
197 | .pads input:checked {
198 |   background-color: var(--boxHigh);
199 | }
200 | 
201 | .pads label {
202 |   display: none;
203 | }
204 | 
205 | /* range input styling ~~~~~~~~~~~~~~~~~~~ */
206 | 
207 | input[type="range"] {
208 |   -webkit-appearance: none;
209 |   background: transparent;
210 | }
211 | 
212 | input[type="range"]::-ms-track {
213 |   width: 100%;
214 |   cursor: pointer;
215 |   background: transparent;
216 |   border-color: transparent;
217 |   color: transparent;
218 | }
219 | 
220 | input[type="range"]::-webkit-slider-thumb {
221 |   -webkit-appearance: none;
222 |   margin-top: -1vh;
223 |   height: 4vh;
224 |   width: 2vw;
225 |   border: 0.5vmin solid var(--black);
226 |   border-radius: var(--borderRad);
227 |   background-color: var(--boxSecond);
228 |   cursor: pointer;
229 | }
230 | 
231 | input[type="range"]::-moz-range-thumb {
232 |   height: 4vh;
233 |   width: 2vw;
234 |   border: 0.5vmin solid var(--black);
235 |   border-radius: var(--borderRad);
236 |   background-color: var(--boxSecond);
237 |   cursor: pointer;
238 | }
239 | 
240 | input[type="range"]::-ms-thumb {
241 |   height: 4vh;
242 |   width: 2vw;
243 |   border: 0.5vmin solid var(--black);
244 |   border-radius: var(--borderRad);
245 |   background-color: var(--boxSecond);
246 |   cursor: pointer;
247 | }
248 | 
249 | input[type="range"]::-webkit-slider-runnable-track {
250 |   height: 2vh;
251 |   cursor: pointer;
252 |   background-color: var(--black);
253 |   border-radius: var(--borderRad);
254 | }
255 | 
256 | input[type="range"]::-moz-range-track {
257 |   height: 2vh;
258 |   cursor: pointer;
259 |   background-color: var(--black);
260 |   border-radius: var(--borderRad);
261 | }
262 | 
263 | input[type="range"]::-ms-track {
264 |   height: 2vh;
265 |   cursor: pointer;
266 |   background-color: var(--black);
267 |   border-radius: var(--borderRad);
268 | }
269 | 
270 | input[type="range"]:focus {
271 |   outline: none;
272 | }
273 | 
274 | input[type="range"]:focus::-webkit-slider-thumb {
275 |   background-color: var(--boxHigh);
276 | }
277 | 
278 | input[type="range"]:focus::-moz-range-thumb {
279 |   background-color: var(--boxHigh);
280 | }
281 | 
282 | input[type="range"]:focus::-ms-thumb {
283 |   background-color: var(--boxHigh);
284 | }
285 | 


--------------------------------------------------------------------------------
/stereo-panner-node/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     Web Audio API examples: StereoPannerNode
 7 |   
 8 | 
 9 |   
10 |     

Web Audio API examples: StereoPannerNode

11 | 16 |

Set stereo panning

17 | 25 | 0 26 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /stereo-panner-node/viper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/stereo-panner-node/viper.mp3 -------------------------------------------------------------------------------- /stereo-panner-node/viper.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/stereo-panner-node/viper.ogg -------------------------------------------------------------------------------- /stream-source-buffer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Web Audio API examples: MediaStreamAudioSourceNode 7 | 8 | 9 | 10 |

Web Audio API examples: MediaStreamAudioSourceNode

11 | 12 |
13 | 14 | 15 | 16 |

Biquad filter frequency response for:

17 | 18 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /violent-theremin/.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 6 | -------------------------------------------------------------------------------- /violent-theremin/README.md: -------------------------------------------------------------------------------- 1 | # violent-theremin 2 | 3 | Violent theremin uses the Web Audio API to generate sound, and HTML5 canvas for a bit of pretty visualization. The colours generated depend on the pitch and gain of the current note, which are themselves dependant on the mouse pointer position. 4 | 5 | You can [view the demo](http://mdn.github.io/webaudio-examples/violent-theremin/) live here. 6 | -------------------------------------------------------------------------------- /violent-theremin/app-icons/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/violent-theremin/app-icons/icon-128.png -------------------------------------------------------------------------------- /violent-theremin/app-icons/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/violent-theremin/app-icons/icon-16.png -------------------------------------------------------------------------------- /violent-theremin/app-icons/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/violent-theremin/app-icons/icon-60.png -------------------------------------------------------------------------------- /violent-theremin/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/violent-theremin/favicon.ico -------------------------------------------------------------------------------- /violent-theremin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Violent Theremin 7 | 8 | 9 | 10 | 11 |

12 | Click on the browser window or press the tab key to start the app. 13 |

14 | 15 |
16 |

Violent Theremin

17 | 18 | 19 | 20 |

21 | Use mouse or arrow keys to control theremin. 22 |

23 | 24 | 25 | Your browser does not support HTML5 canvas 26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /violent-theremin/manifest.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "name": "Violent Theremin", 4 | "description": "A sample MDN app that uses Web Audio API to generate an audio tone, the pitch and volume of which is varied by the movement of the mouse and control keys. It also uses HTML5 Canvas to generate some 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 Web Audio API to generate an audio tone, the pitch and volume of which is varied by the movement of the mouse and control keys. It also uses HTML5 Canvas to generate some visualizations.", 17 | "developer": { 18 | "url": "https://developer.mozila.org" 19 | } 20 | }, 21 | "it": { 22 | "description": "A sample MDN app that uses Web Audio API to generate an audio tone, the pitch and volume of which is varied by the movement of the mouse and control keys. It also uses HTML5 Canvas to generate some visualizations.", 23 | "developer": { 24 | "url": "https://developer.mozila.org" 25 | } 26 | } 27 | }, 28 | "default_locale": "en", 29 | "permissions": {}, 30 | "orientation": "landscape" 31 | } 32 | -------------------------------------------------------------------------------- /violent-theremin/scripts/app.js: -------------------------------------------------------------------------------- 1 | const appContents = document.querySelector(".app-contents"); 2 | const startMessage = document.querySelector(".start-message"); 3 | let isAppInit = false; 4 | appContents.style.display = "none"; 5 | 6 | window.addEventListener("keydown", init); 7 | window.addEventListener("click", init); 8 | 9 | function init() { 10 | if (isAppInit) { 11 | return; 12 | } 13 | 14 | appContents.style.display = "block"; 15 | document.body.removeChild(startMessage); 16 | 17 | // create web audio api context 18 | const AudioContext = window.AudioContext || window.webkitAudioContext; 19 | const audioCtx = new AudioContext(); 20 | 21 | // create Oscillator and gain node 22 | const oscillator = audioCtx.createOscillator(); 23 | const gainNode = audioCtx.createGain(); 24 | 25 | // connect oscillator to gain node to speakers 26 | oscillator.connect(gainNode); 27 | gainNode.connect(audioCtx.destination); 28 | 29 | // create initial theremin frequency and volume values 30 | const WIDTH = window.innerWidth; 31 | const HEIGHT = window.innerHeight; 32 | 33 | const maxFreq = 6000; 34 | const maxVol = 0.02; 35 | const initialVol = 0.001; 36 | 37 | // set options for the oscillator 38 | oscillator.detune.value = 100; // value in cents 39 | oscillator.start(0); 40 | 41 | oscillator.onended = function () { 42 | console.log("Your tone has now stopped playing!"); 43 | }; 44 | 45 | gainNode.gain.value = initialVol; 46 | gainNode.gain.minValue = initialVol; 47 | gainNode.gain.maxValue = initialVol; 48 | 49 | // Mouse pointer coordinates 50 | let CurX; 51 | let CurY; 52 | 53 | // Get new mouse pointer coordinates when mouse is moved 54 | // then set new gain and pitch values 55 | document.onmousemove = updatePage; 56 | 57 | function updatePage(e) { 58 | KeyFlag = false; 59 | 60 | CurX = e.pageX; 61 | CurY = e.pageY; 62 | 63 | oscillator.frequency.value = (CurX / WIDTH) * maxFreq; 64 | gainNode.gain.value = (CurY / HEIGHT) * maxVol; 65 | 66 | canvasDraw(); 67 | } 68 | 69 | // mute button 70 | const mute = document.querySelector(".mute"); 71 | 72 | mute.onclick = function () { 73 | if (mute.getAttribute("data-muted") === "false") { 74 | gainNode.disconnect(audioCtx.destination); 75 | mute.setAttribute("data-muted", "true"); 76 | mute.innerHTML = "Unmute"; 77 | } else { 78 | gainNode.connect(audioCtx.destination); 79 | mute.setAttribute("data-muted", "false"); 80 | mute.innerHTML = "Mute"; 81 | } 82 | }; 83 | 84 | // canvas visualization 85 | function random(number1, number2) { 86 | return number1 + (Math.floor(Math.random() * (number2 - number1)) + 1); 87 | } 88 | 89 | const canvas = document.querySelector(".canvas"); 90 | const canvasCtx = canvas.getContext("2d"); 91 | 92 | canvas.width = WIDTH; 93 | canvas.height = HEIGHT; 94 | 95 | function canvasDraw() { 96 | if (KeyFlag) { 97 | rX = KeyX; 98 | rY = KeyY; 99 | } else { 100 | rX = CurX; 101 | rY = CurY; 102 | } 103 | 104 | rC = Math.floor((gainNode.gain.value / maxVol) * 30); 105 | 106 | canvasCtx.globalAlpha = 0.2; 107 | 108 | for (let i = 1; i <= 15; i = i + 2) { 109 | canvasCtx.beginPath(); 110 | canvasCtx.fillStyle = 111 | "rgb(" + 112 | 100 + 113 | i * 10 + 114 | "," + 115 | Math.floor((gainNode.gain.value / maxVol) * 255) + 116 | "," + 117 | Math.floor((oscillator.frequency.value / maxFreq) * 255) + 118 | ")"; 119 | canvasCtx.arc( 120 | rX + random(0, 50), 121 | rY + random(0, 50), 122 | rC / 2 + i, 123 | (Math.PI / 180) * 0, 124 | (Math.PI / 180) * 360, 125 | false 126 | ); 127 | canvasCtx.fill(); 128 | canvasCtx.closePath(); 129 | } 130 | } 131 | 132 | // clear screen 133 | const clear = document.querySelector(".clear"); 134 | 135 | clear.onclick = function () { 136 | canvasCtx.clearRect(0, 0, canvas.width, canvas.height); 137 | }; 138 | 139 | // keyboard controls 140 | const body = document.querySelector("body"); 141 | 142 | let KeyX = 1; 143 | let KeyY = 0.01; 144 | let KeyFlag = false; 145 | 146 | const ARROW_LEFT = "ArrowLeft"; 147 | const ARROW_RIGHT = "ArrowRight"; 148 | const ARROW_UP = "ArrowUp"; 149 | const ARROW_DOWN = "ArrowDown"; 150 | 151 | body.onkeydown = function (e) { 152 | KeyFlag = true; 153 | 154 | if (e.code === ARROW_LEFT) { 155 | KeyX -= 20; 156 | } 157 | 158 | if (e.code === ARROW_RIGHT) { 159 | KeyX += 20; 160 | } 161 | 162 | if (e.code === ARROW_UP) { 163 | KeyY -= 20; 164 | } 165 | 166 | if (e.code === ARROW_DOWN) { 167 | KeyY += 20; 168 | } 169 | 170 | // set max and min constraints for KeyX and KeyY 171 | if (KeyX < 1) { 172 | KeyX = 1; 173 | } 174 | 175 | if (KeyX > WIDTH) { 176 | KeyX = WIDTH; 177 | } 178 | 179 | if (KeyY < 0.01) { 180 | KeyY = 0.01; 181 | } 182 | 183 | if (KeyY > HEIGHT) { 184 | KeyY = HEIGHT; 185 | } 186 | 187 | oscillator.frequency.value = (KeyX / WIDTH) * maxFreq; 188 | gainNode.gain.value = (KeyY / HEIGHT) * maxVol; 189 | 190 | canvasDraw(); 191 | }; 192 | 193 | isAppInit = true; 194 | } 195 | -------------------------------------------------------------------------------- /violent-theremin/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 | -------------------------------------------------------------------------------- /violent-theremin/styles/app.css: -------------------------------------------------------------------------------- 1 | /* general styling */ 2 | 3 | html { 4 | font-size: 10px; 5 | height: 100%; 6 | } 7 | 8 | body { 9 | width: 100%; 10 | height: inherit; 11 | } 12 | 13 | canvas { 14 | display: block; 15 | } 16 | 17 | /* header styling */ 18 | 19 | h1 { 20 | position: absolute; 21 | bottom: 10px; 22 | right: 15px; 23 | font-size: 3rem; 24 | margin: 0; 25 | color: white; 26 | text-shadow: 0 0 10px rgba(0, 0, 0, 0.3); 27 | } 28 | 29 | /* button styling */ 30 | 31 | button { 32 | background-color: #0088cc; 33 | background-image: linear-gradient(to bottom, #0088cc 0%, #0055cc 100%); 34 | text-shadow: 1px 1px 1px black; 35 | text-align: center; 36 | color: white; 37 | border: none; 38 | width: 150px; 39 | font-size: 1.6rem; 40 | line-height: 2rem; 41 | padding: 0.5rem; 42 | display: block; 43 | opacity: 0.3; 44 | 45 | transition: 1s all; 46 | -webkit-transition: 1s all; 47 | 48 | position: absolute; 49 | } 50 | 51 | button:hover, 52 | button:focus { 53 | box-shadow: inset 1px 1px 2px rgba(0, 0, 0, 0.7); 54 | opacity: 1; 55 | } 56 | 57 | button:active { 58 | box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.7); 59 | } 60 | 61 | #activated { 62 | background-color: red; 63 | color: white; 64 | } 65 | 66 | .mute { 67 | top: 41px; 68 | left: 5px; 69 | } 70 | 71 | .clear { 72 | top: 6px; 73 | left: 5px; 74 | } 75 | 76 | .start-message { 77 | position: absolute; 78 | font-size: 1.4rem; 79 | left: 5px; 80 | } 81 | 82 | .control-message { 83 | position: absolute; 84 | font-size: 1.4rem; 85 | top: 70px; 86 | left: 5px; 87 | } 88 | -------------------------------------------------------------------------------- /violent-theremin/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 | -------------------------------------------------------------------------------- /voice-change-o-matic/README.md: -------------------------------------------------------------------------------- 1 | # Voice-change-o-matic 2 | 3 | A Web Audio API-powered voice changer and visualizer. 4 | 5 | [Run the demo live](http://mdn.github.io/webaudio-examples/voice-change-o-matic/) 6 | -------------------------------------------------------------------------------- /voice-change-o-matic/app-icons/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/voice-change-o-matic/app-icons/icon-128.png -------------------------------------------------------------------------------- /voice-change-o-matic/app-icons/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/voice-change-o-matic/app-icons/icon-16.png -------------------------------------------------------------------------------- /voice-change-o-matic/app-icons/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/voice-change-o-matic/app-icons/icon-60.png -------------------------------------------------------------------------------- /voice-change-o-matic/audio/concert-crowd.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/voice-change-o-matic/audio/concert-crowd.mp3 -------------------------------------------------------------------------------- /voice-change-o-matic/audio/concert-crowd.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/voice-change-o-matic/audio/concert-crowd.ogg -------------------------------------------------------------------------------- /voice-change-o-matic/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/voice-change-o-matic/favicon.ico -------------------------------------------------------------------------------- /voice-change-o-matic/images/pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/webaudio-examples/7a37eb418ff381028674b95a7c9a86f893542a80/voice-change-o-matic/images/pattern.png -------------------------------------------------------------------------------- /voice-change-o-matic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Voice-change-O-matic 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |

Voice-change-O-matic

21 |
22 | 23 | 24 | 25 |
26 |
27 | 28 | 35 |
36 |
37 | 38 | 43 |
44 |
45 | Mute 46 |
47 |
48 |
49 | 50 | 51 | 52 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /voice-change-o-matic/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 | -------------------------------------------------------------------------------- /voice-change-o-matic/scripts/app.js: -------------------------------------------------------------------------------- 1 | const heading = document.querySelector("h1"); 2 | heading.textContent = "CLICK HERE TO START"; 3 | document.body.addEventListener("click", init); 4 | 5 | async function init() { 6 | heading.textContent = "Voice-change-O-matic"; 7 | document.body.removeEventListener("click", init); 8 | 9 | const audioCtx = new AudioContext(); 10 | const voiceSelect = document.getElementById("voice"); 11 | let source; 12 | let stream; 13 | 14 | // Grab the mute button to use below 15 | const mute = document.querySelector(".mute"); 16 | 17 | // Set up the different audio nodes we will use for the app 18 | const analyser = audioCtx.createAnalyser(); 19 | analyser.minDecibels = -90; 20 | analyser.maxDecibels = -10; 21 | analyser.smoothingTimeConstant = 0.85; 22 | 23 | const distortion = audioCtx.createWaveShaper(); 24 | const gainNode = audioCtx.createGain(); 25 | const biquadFilter = audioCtx.createBiquadFilter(); 26 | const convolver = audioCtx.createConvolver(); 27 | 28 | const echoDelay = createEchoDelayEffect(audioCtx); 29 | 30 | // Distortion curve for the waveshaper, thanks to Kevin Ennis 31 | // http://stackoverflow.com/questions/22312841/waveshaper-node-in-webaudio-how-to-emulate-distortion 32 | function makeDistortionCurve(amount) { 33 | let k = typeof amount === "number" ? amount : 50, 34 | n_samples = 44100, 35 | curve = new Float32Array(n_samples), 36 | deg = Math.PI / 180, 37 | i = 0, 38 | x; 39 | for (; i < n_samples; ++i) { 40 | x = (i * 2) / n_samples - 1; 41 | curve[i] = ((3 + k) * x * 20 * deg) / (Math.PI + k * Math.abs(x)); 42 | } 43 | return curve; 44 | } 45 | 46 | // Grab audio track via fetch() for convolver node 47 | try { 48 | const response = await fetch( 49 | "https://mdn.github.io/voice-change-o-matic/audio/concert-crowd.ogg" 50 | ); 51 | const arrayBuffer = await response.arrayBuffer(); 52 | const decodedAudio = await audioCtx.decodeAudioData(arrayBuffer); 53 | convolver.buffer = decodedAudio; 54 | } catch (error) { 55 | console.error( 56 | `Unable to fetch the audio file: ${name} Error: ${error.message}` 57 | ); 58 | } 59 | 60 | // Set up canvas context for visualizer 61 | const canvas = document.querySelector(".visualizer"); 62 | const canvasCtx = canvas.getContext("2d"); 63 | 64 | const intendedWidth = document.querySelector(".wrapper").clientWidth; 65 | canvas.setAttribute("width", intendedWidth); 66 | const visualSelect = document.getElementById("visual"); 67 | let drawVisual; 68 | 69 | // Main block for doing the audio recording 70 | const constraints = { audio: true }; 71 | navigator.mediaDevices 72 | .getUserMedia(constraints) 73 | .then((stream) => { 74 | source = audioCtx.createMediaStreamSource(stream); 75 | source.connect(distortion); 76 | distortion.connect(biquadFilter); 77 | biquadFilter.connect(gainNode); 78 | convolver.connect(gainNode); 79 | echoDelay.placeBetween(gainNode, analyser); 80 | analyser.connect(audioCtx.destination); 81 | 82 | visualize(); 83 | voiceChange(); 84 | }) 85 | .catch(function (err) { 86 | console.error("The following gUM error occured: " + err); 87 | }); 88 | 89 | function visualize() { 90 | const WIDTH = canvas.width; 91 | const HEIGHT = canvas.height; 92 | 93 | const visualSetting = visualSelect.value; 94 | console.log(visualSetting); 95 | 96 | if (visualSetting === "sinewave") { 97 | analyser.fftSize = 2048; 98 | const bufferLength = analyser.fftSize; 99 | console.log(bufferLength); 100 | 101 | // We can use Float32Array instead of Uint8Array if we want higher precision 102 | // const dataArray = new Float32Array(bufferLength); 103 | const dataArray = new Uint8Array(bufferLength); 104 | 105 | canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); 106 | 107 | const draw = () => { 108 | drawVisual = requestAnimationFrame(draw); 109 | 110 | analyser.getByteTimeDomainData(dataArray); 111 | 112 | canvasCtx.fillStyle = "rgb(200, 200, 200)"; 113 | canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); 114 | 115 | canvasCtx.lineWidth = 2; 116 | canvasCtx.strokeStyle = "rgb(0, 0, 0)"; 117 | 118 | canvasCtx.beginPath(); 119 | 120 | const sliceWidth = (WIDTH * 1.0) / bufferLength; 121 | let x = 0; 122 | 123 | for (let i = 0; i < bufferLength; i++) { 124 | const v = dataArray[i] / 128.0; 125 | const y = (v * HEIGHT) / 2; 126 | 127 | if (i === 0) { 128 | canvasCtx.moveTo(x, y); 129 | } else { 130 | canvasCtx.lineTo(x, y); 131 | } 132 | 133 | x += sliceWidth; 134 | } 135 | 136 | canvasCtx.lineTo(WIDTH, HEIGHT / 2); 137 | canvasCtx.stroke(); 138 | }; 139 | 140 | draw(); 141 | } else if (visualSetting == "frequencybars") { 142 | analyser.fftSize = 256; 143 | const bufferLengthAlt = analyser.frequencyBinCount; 144 | console.log(bufferLengthAlt); 145 | 146 | // See comment above for Float32Array() 147 | const dataArrayAlt = new Uint8Array(bufferLengthAlt); 148 | 149 | canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); 150 | 151 | const drawAlt = () => { 152 | drawVisual = requestAnimationFrame(drawAlt); 153 | 154 | analyser.getByteFrequencyData(dataArrayAlt); 155 | 156 | canvasCtx.fillStyle = "rgb(0, 0, 0)"; 157 | canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); 158 | 159 | const barWidth = (WIDTH / bufferLengthAlt) * 2.5; 160 | let x = 0; 161 | 162 | for (let i = 0; i < bufferLengthAlt; i++) { 163 | const barHeight = dataArrayAlt[i]; 164 | 165 | canvasCtx.fillStyle = "rgb(" + (barHeight + 100) + ",50,50)"; 166 | canvasCtx.fillRect( 167 | x, 168 | HEIGHT - barHeight / 2, 169 | barWidth, 170 | barHeight / 2 171 | ); 172 | 173 | x += barWidth + 1; 174 | } 175 | }; 176 | 177 | drawAlt(); 178 | } else if (visualSetting == "off") { 179 | canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); 180 | canvasCtx.fillStyle = "red"; 181 | canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); 182 | } 183 | } 184 | 185 | function voiceChange() { 186 | distortion.oversample = "4x"; 187 | biquadFilter.gain.setTargetAtTime(0, audioCtx.currentTime, 0); 188 | 189 | const voiceSetting = voiceSelect.value; 190 | console.log(voiceSetting); 191 | 192 | if (echoDelay.isApplied()) { 193 | echoDelay.discard(); 194 | } 195 | 196 | // When convolver is selected it is connected back into the audio path 197 | if (voiceSetting == "convolver") { 198 | biquadFilter.disconnect(0); 199 | biquadFilter.connect(convolver); 200 | } else { 201 | biquadFilter.disconnect(0); 202 | biquadFilter.connect(gainNode); 203 | 204 | if (voiceSetting == "distortion") { 205 | distortion.curve = makeDistortionCurve(400); 206 | } else if (voiceSetting == "biquad") { 207 | biquadFilter.type = "lowshelf"; 208 | biquadFilter.frequency.setTargetAtTime(1000, audioCtx.currentTime, 0); 209 | biquadFilter.gain.setTargetAtTime(25, audioCtx.currentTime, 0); 210 | } else if (voiceSetting == "delay") { 211 | echoDelay.apply(); 212 | } else if (voiceSetting == "off") { 213 | console.log("Voice settings turned off"); 214 | } 215 | } 216 | } 217 | 218 | function createEchoDelayEffect(audioContext) { 219 | const delay = audioContext.createDelay(1); 220 | const dryNode = audioContext.createGain(); 221 | const wetNode = audioContext.createGain(); 222 | const mixer = audioContext.createGain(); 223 | const filter = audioContext.createBiquadFilter(); 224 | 225 | delay.delayTime.value = 0.75; 226 | dryNode.gain.value = 1; 227 | wetNode.gain.value = 0; 228 | filter.frequency.value = 1100; 229 | filter.type = "highpass"; 230 | return { 231 | apply() { 232 | wetNode.gain.setValueAtTime(0.75, audioContext.currentTime); 233 | }, 234 | discard() { 235 | wetNode.gain.setValueAtTime(0, audioContext.currentTime); 236 | }, 237 | isApplied() { 238 | return wetNode.gain.value > 0; 239 | }, 240 | placeBetween(inputNode, outputNode) { 241 | inputNode.connect(delay); 242 | delay.connect(wetNode); 243 | wetNode.connect(filter); 244 | filter.connect(delay); 245 | 246 | inputNode.connect(dryNode); 247 | dryNode.connect(mixer); 248 | wetNode.connect(mixer); 249 | mixer.connect(outputNode); 250 | }, 251 | }; 252 | } 253 | 254 | // Event listeners to change visualize and voice settings 255 | visualSelect.addEventListener("change", () => { 256 | cancelAnimationFrame(drawVisual); 257 | visualize(); 258 | }); 259 | 260 | voiceSelect.addEventListener("change", () => { 261 | voiceChange(); 262 | }); 263 | 264 | mute.addEventListener("click", () => { 265 | if (mute.id === "") { 266 | gainNode.gain.value = 0; 267 | mute.id = "activated"; 268 | mute.innerHTML = "Unmute"; 269 | } else { 270 | gainNode.gain.value = 1; 271 | mute.id = ""; 272 | mute.innerHTML = "Mute"; 273 | } 274 | }); 275 | } 276 | -------------------------------------------------------------------------------- /voice-change-o-matic/scripts/install.js: -------------------------------------------------------------------------------- 1 | // get a reference to the install button 2 | var button = document.getElementById("install-btn"); 3 | 4 | if (navigator.mozApps) { 5 | var manifest_url = location.href + "manifest.webapp"; 6 | 7 | function install(ev) { 8 | ev.preventDefault(); 9 | // define the manifest URL 10 | // install the app 11 | var installLocFind = navigator.mozApps.install(manifest_url); 12 | installLocFind.onsuccess = function (data) { 13 | // App is installed, do something 14 | }; 15 | installLocFind.onerror = function () { 16 | // App wasn't installed, info is in 17 | // installapp.error.name 18 | alert(installLocFind.error.name); 19 | }; 20 | } 21 | 22 | //call install() on click if the app isn't already installed. If it is, hide the button. 23 | 24 | var installCheck = navigator.mozApps.checkInstalled(manifest_url); 25 | 26 | installCheck.onsuccess = function () { 27 | if (installCheck.result) { 28 | button.style.display = "none"; 29 | } else { 30 | button.addEventListener("click", install, false); 31 | } 32 | }; 33 | } else { 34 | button.style.display = "none"; 35 | } 36 | -------------------------------------------------------------------------------- /voice-change-o-matic/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, 18 | h2, 19 | label { 20 | font-size: 3rem; 21 | font-family: "Nova Square", cursive; 22 | text-align: center; 23 | color: black; 24 | text-shadow: -1px -1px 1px #aaa, 0px 1px 1px rgba(255, 255, 255, 0.5), 25 | 1px 1px 2px rgba(255, 255, 255, 0.7), 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), 0 3px 4px rgba(0, 0, 0, 0.7); 51 | } 52 | 53 | .controls { 54 | background-color: rgba(0, 0, 0, 0.1); 55 | height: calc(100% - 225px); 56 | } 57 | 58 | /* || select element styling */ 59 | 60 | .controls div { 61 | width: 100%; 62 | padding-top: 1rem; 63 | } 64 | 65 | .controls label, 66 | .controls select { 67 | display: block; 68 | margin: 0 auto; 69 | } 70 | 71 | .controls label { 72 | width: 100%; 73 | text-align: center; 74 | line-height: 3rem; 75 | padding: 1rem 0; 76 | } 77 | 78 | .controls select { 79 | width: 80%; 80 | font-size: 2rem; 81 | } 82 | 83 | /* || button styling */ 84 | 85 | button, 86 | 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: 0.5rem; 99 | display: block; 100 | } 101 | 102 | button:hover, 103 | button:focus, 104 | form a:hover, 105 | form a:focus { 106 | box-shadow: inset 1px 1px 2px rgba(0, 0, 0, 0.7); 107 | } 108 | 109 | button:active, 110 | form a:active { 111 | box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.7); 112 | } 113 | 114 | a#activated { 115 | background-color: #fff; 116 | background-image: linear-gradient(to bottom, #f00 0%, #a06 100%); 117 | } 118 | 119 | /* || Checkbox hack to control information box display */ 120 | 121 | label[for="toggle"] { 122 | font-family: "NotoColorEmoji"; 123 | font-size: 3rem; 124 | position: absolute; 125 | top: 4px; 126 | right: 5px; 127 | z-index: 5; 128 | cursor: pointer; 129 | } 130 | 131 | input[type="checkbox"] { 132 | position: absolute; 133 | top: -100px; 134 | } 135 | 136 | aside { 137 | position: fixed; 138 | top: 0; 139 | left: 0; 140 | padding-top: 1.5rem; 141 | text-shadow: 1px 1px 1px black; 142 | width: 100%; 143 | height: 100%; 144 | transform: translateX(100%); 145 | transition: 0.6s all; 146 | background-color: #999; 147 | background-image: linear-gradient( 148 | to top right, 149 | rgba(0, 0, 0, 0), 150 | rgba(0, 0, 0, 0.5) 151 | ); 152 | } 153 | 154 | aside p, 155 | aside li { 156 | font-size: 1.6rem; 157 | line-height: 1.3; 158 | padding: 0rem 2rem 1rem; 159 | color: white; 160 | } 161 | 162 | aside li { 163 | padding-left: 10px; 164 | } 165 | 166 | /* Toggled State of information box */ 167 | 168 | input[type="checkbox"]:checked ~ aside { 169 | transform: translateX(0); 170 | } 171 | 172 | /* || Link styles */ 173 | 174 | a { 175 | color: #aaa; 176 | } 177 | 178 | a:hover, 179 | a:focus { 180 | text-decoration: none; 181 | } 182 | 183 | @media (min-width: 481px) { 184 | /*CSS for medium width screens*/ 185 | 186 | /* || Basic layout changes for the main control buttons */ 187 | } 188 | 189 | @media all and (min-width: 800px) { 190 | /*CSS for wide screens*/ 191 | 192 | h1 { 193 | font-size: 5rem; 194 | padding-top: 2.5rem; 195 | } 196 | 197 | aside { 198 | top: 0; 199 | left: 100%; 200 | text-shadow: 1px 1px 1px black; 201 | width: 480px; 202 | transform: translateX(0); 203 | border-left: 2px solid black; 204 | } 205 | 206 | /* Toggled State of information box */ 207 | 208 | input[type="checkbox"]:checked ~ aside { 209 | transform: translateX(-480px); 210 | } 211 | } 212 | 213 | @media (min-width: 1100px) { 214 | /*CSS for really wide screens*/ 215 | } 216 | -------------------------------------------------------------------------------- /voice-change-o-matic/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( 5 | linear, 6 | left top, 7 | left bottom, 8 | color-stop(0%, #0088cc), 9 | color-stop(100%, #0055cc) 10 | ); /* Chrome,Safari4+ */ 11 | background: -webkit-linear-gradient( 12 | top, 13 | #0088cc 0%, 14 | #0055cc 100% 15 | ); /* Chrome10+,Safari5.1+ */ 16 | background: -o-linear-gradient( 17 | top, 18 | #0088cc 0%, 19 | #0055cc 100% 20 | ); /* Opera 11.10+ */ 21 | background: -ms-linear-gradient(top, #0088cc 0%, #0055cc 100%); /* IE10+ */ 22 | background: linear-gradient(to bottom, #0088cc 0%, #0055cc 100%); /* W3C */ 23 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#0088cc', endColorstr='#0055cc',GradientType=0 ); /* IE6-9 */ 24 | 25 | text-align: center; 26 | font-size: 200%; 27 | margin: 1em auto; 28 | display: block; 29 | padding: 0.5em; 30 | color: white; 31 | width: 10em; 32 | max-width: 80%; 33 | line-height: 1.2em; 34 | } 35 | -------------------------------------------------------------------------------- /voice-change-o-matic/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 | --------------------------------------------------------------------------------