Upload
others
View
5
Download
2
Embed Size (px)
Citation preview
JavaScriptforSoundArtistsLearntoCodewiththeWebAudioAPI
JavaScriptforSoundArtistsLearntoCodewiththeWebAudioAPI
Authoredby:WilliamTurnerEditedby:SteveLeonard
CRCPressTaylor&FrancisGroup6000BrokenSoundParkwayNW,Suite300BocaRaton,FL33487-2742
©2017byTaylor&FrancisGroup,LLCCRCPressisanimprintofTaylor&FrancisGroup,anInformabusiness
NoclaimtooriginalU.S.Governmentworks
Printedonacid-freepaperVersionDate:20161208
InternationalStandardBookNumber-13:978-1-138-96153-1(Paperback)
Thisbookcontainsinformationobtainedfromauthenticandhighlyregardedsources.Reasonableeffortshavebeenmadetopublishreliable data and information, but the author and publisher cannot assume responsibility for the validity of all materials or theconsequencesoftheiruse.Theauthorsandpublishershaveattemptedtotracethecopyrightholdersofallmaterialreproducedinthis publication and apologize to copyright holders if permission to publish in this form has not been obtained. If any copyrightmaterialhasnotbeenacknowledgedpleasewriteandletusknowsowemayrectifyinanyfuturereprint.
ExceptaspermittedunderU.S.CopyrightLaw,nopartof thisbookmaybe reprinted, reproduced, transmitted,orutilized inanyformbyanyelectronic,mechanical, orothermeans,nowknownorhereafter invented, includingphotocopying,microfilming, andrecording,orinanyinformationstorageorretrievalsystem,withoutwrittenpermissionfromthepublishers.
For permission to photocopy or use material electronically from this work, please access www.copyright.com(http://www.copyright.com/)orcontact theCopyrightClearanceCenter, Inc. (CCC),222RosewoodDrive,Danvers,MA01923,978-750-8400.CCCisanot-for-profitorganizationthatprovideslicensesandregistrationforavarietyofusers.FororganizationsthathavebeengrantedaphotocopylicensebytheCCC,aseparatesystemofpaymenthasbeenarranged.
Trademarknotice:Productorcorporatenamesmaybetrademarksorregisteredtrademarks,andareusedonlyforidentificationandexplanationwithoutintenttoinfringe.
LibraryofCongressCataloging-in-PublicationData
Names:Turner,William(Websitedeveloper),author.|Leonard,Steve(Websitedeveloper),author.Title:JavaScriptforsoundartists:learntocodewiththeWebAudioAPI/WilliamTurner,SteveLeonard.Description:BocaRaton:Taylor&Francis,CRCPress,2017.Identifiers:LCCN2016032832|ISBN9781138961531(pbk.:alk.paper)|ISBN9781138731134(hardback:alk.paper)Subjects:LCSH:Computersoundprocessing.|JavaScript(Computerprogramlanguage)|Webcasting.Classification:LCCTK7881.4.T872017|DDC006.5--dc23LCrecordavailableathttps://lccn.loc.gov/2016032832
VisittheTaylor&FrancisWebsiteathttp://www.taylorandfrancis.com
andtheCRCPressWebsiteathttp://www.crcpress.com
Contents
Preface
Acknowledgment
1.OverviewandSetupWhatIsaProgram?WhatIsJavaScript?HTML,CSS,andJavaScriptWhatIsaWebApplication?WhatIstheWebAudioAPI?SettingUpYourWorkEnvironment
SetupViewinBrowserforWindowsSetupViewinBrowserforMac
HowtoCreateCodeSnippetsAccessingtheChromeDeveloperToolsTroubleshootingProblemsandGettingHelp
2.GettingStartedwithJavaScriptandtheWebAudioAPIHelloSoundProgramVariablesnullDocumentingYourCodewithCommentsExploringVariableswithanOscillatorconsole.log()String
Built-InStringMethods
toUpperCase()toLowerCase()charAt()replace()slice()
ThelengthPropertyNumbers
HowtoDeterminetheDataTypeofaVariableExamplesofArithmeticOperatorsExamplesofPrecedenceMath.min()andMath.max()Math.ceil()andMath.floor()Math.random()Math.abs()
Number-to-StringConversionArrayspush()pop()shift()unshift()concat()
Summary
3.OperatorsWhatAreOperators?
AssignmentOperatorsAssignmentAdditionAssignmentSubtractionAssignmentMultiplicationAssignmentDivisionAssignmentModuloAssignment
TheBooleanDataTypeComparisonOperators
EqualityOperatorStrictEqualityOperatorGreaterThanandLessThanOperatorsGreaterThanorEqualtoOperatorLessThanorEqualtoOperatorNotEqualtoOperator
StrictNotEqualtoOperatorLogicalOperatorsTheLogicalANDOperatorTheLogicalOROperatorTheNOTOperator
Summary
4.ConditionalStatementsandLoopsConditionalStatements
TheifStatementTheswitchStatementTernaryOperator
LoopsforLoops
UsingforLoopswithArrayswhileLoopsWhentoUseforLoopsandWhentoUsewhileLoops
Summary
5.FunctionsFunctions—ASimpleExample
PartsofaFunctionFunctionExpressionsAbstractingOscillatorPlaybackAWorkingEffectsBoxExampleTheArgumentsObject
FunctionScopeWhyYouShouldAlwaysDeclareYourVariableswithvar
VariableHoistingHowHoistingAffectsFunctions
AnonymousFunctionsClosures
WhatIsaClosure?CallbackFunctions
WorkingwithJavaScript’sBuilt-InCallbackFunctionsfilter()map()
Recursion
Summary
6.ObjectsJavaScriptDataTypesLoopingthroughObjectsWhentoUseObjectsRatherThanArraysHowtoCheckIfanObjectHasAccesstoaParticularPropertyorMethodCloningObjectsPrototypalInheritanceThe"this"KeywordThebindFunctionSummary
7.NodeGraphsandOscillatorsTheAudioContext()MethodNodeGraphsOscillatorsThestopMethodTheonendedPropertyHowtoStopOscillatorsandRestartThemThetypePropertyThefrequencyPropertyThedetunePropertySummary
8.UsingHTMLandCSStoBuildUserInterfacesWhatIsaUserInterface?
HTMLExplanationoftheHTMLTemplateUnderstandingHTMLElementsFormandInputElements
CSSCommentsElementSelectorsGroupingSelectorsDescendentSelectorsChildSelectors
classandidModifyingtheAppInterfaceMargin,Border,andPaddingRemovingListElementBulletPointsFontSize,Style(Type),andColorCenteringBlock-LevelElementsSummary
9.DOMProgrammingwithJavaScriptHowDoesJavaScriptCommunicatewiththeDOM?
HTMLJavaScript
BuildingtheApplicationHowtoTriggeranOscillatorbyClickingaButtonTogglingtheStart/StopTextProgrammingtheFrequencySliderChangingtheFrequencyinRealTimeChangingWaveformTypesCompletedCodewithWaveformSelectionGivinganOutlinetotheSelectedWaveformTypeSummary
10.SimplifyingDOMProgrammingwithJQueryWhatIsJQuery?JQuerySetupReferencingJQueryDirectlyUsingJQueryfromaCDNHowtoUseJQuerySelectingHTMLElementsStoringDOMSelectorsasVariablesUsingMethods
HTMLJQuery/JavaScripttoChangeaSinglePropertyJQuery/JavaScripttoChangeMultipleProperties
MethodChainingHTMLCSSJQuery/JavaScriptHTML
JQuery/JavaScriptThethisKeyword
HTMLJQuery/JavaScript
RefactoringtheOscillatorPlayertoUseJQueryWithoutJQueryWithJQuery
SettingUptheEventListenerfortheUser-SelectedListElementEventListenerwithoutJQueryEventListenerwithJQuery
ModifyingtheCodeinsetIntervalsetIntervalMethodwithoutJQuerysetIntervalMethodwithJQueryonOffMethodwithoutJQuery$onOffSelectorwithJQuery
Summary
11.LoadingandPlayingAudioFilesPrerequisitesTheTwoStepstoLoadinganAudioFileTheXMLHttpRequestObjectgetRequests
AWordonAudioFileTypeCompatibilitySynchronousversusAsynchronousCodeExecutionProcessingtheAudioBufferwiththeNodeGraphSummary
12.FactoriesandConstructorsJavaScriptandtheConceptofClassWhatAreClasses?TheFactoryPatternDynamicObjectExtensionPrivateDataGettersandSettersConstructorsandthenewKeywordAddingMethodstoConstructorsThePrototypeObjectandthePrototypePropertyWhyDoConstructorsExistIfYouCanDotheSameThingwithFactories?
Summary
13.AbstractingtheFileLoaderThinkingaboutCodeAbstractionCreatingtheAbstractionWalkingthroughtheCodeSummary
14.TheNodeGraphandWorkingwithEffectsHowtoThinkAbouttheNodeGraphGainNodesThePlacementofNodesIsUptoYouWhatEffectsAreAvailable?HowtoDeterminetheNodesYouNeedtoCreatetheEffectYouWantAReal-WorldExampleSomeEffectsRequireDevelopmentWorkSummary
15.TheBiquadFilterNodeUsingtheBiquadFilterNodeFilterTypesCreatinganEqualizerGraphicEQParametricEQSummary
16.TheConvolverNodeConvolutionReverbWheretoGetPre-RecordedImpulseResponseFilesUsingImpulseResponseFiles
HTMLJavaScript
ControllingtheAmountofReverberationSummary
17.StereoPanning,ChannelSplitting,andMergingTheStereoPannerNode
TheChannelSplitterTheChannelMergerMergingAllChannelsofaMultichannelFileintoaSingleMonoChannelUsingtheMergerandSplitterNodesTogetherSummary
18.TheDelayNodeTheDelayNodeCreatingEchoEffectsCreatingSlapBackEffectsCreatingaPing-PongDelaySummary
19.DynamicRangeCompressionTheDynamicsCompressorNodeSummary
20.TimeTheTimingClockThestartMethodLoopingSoundsUpdateYourAudioLoaderLibraryChangingAudioParametersoverTimeTheAudioParameterMethods
ThesetValueAtTimeMethodTheexponentialRampToValueAtTimeMethodThelinearRampToValueAtTimeMethodThesetTargetAtTime()MethodThesetValueCurveAtTime()Method
Summary
21.CreatingAudioVisualizationsABriefWordonFourierAnalysisABriefExplanationofBinary-CodedDecimalNumbersTheSpectrumAnalyzer
JavaScript/JQueryHTML
CSSWalkingthroughtheCodeStoringtheFrequencyDatainanArrayHowtoThinkAboutthefrequencyDataArray
BuildingtheDisplayInterfaceConnectingtheAnalyzertotheDOMSummary
22.AddingFlexibilitytotheAudioLoaderAbstractionTheUpdatedInterfaceModifyingtheLibraryModifyingaudioBatchLoaderAnExplanationofthePreviousCodeEditSummary
23.BuildingaStepSequencerTheProblemCanIUsesetIntervalorsetTimeout?TheSolutionHowItWorksChangingTempoBuildingtheSequencerPlayingBackSoundsinSequenceCreatingtheUserInterfaceGrid
HTMLCSS
AddingInteractivitytotheGridElementsSummary
24.AJAXandJSONAJAXJSONMakinganAJAXCalltotheiTunesSearchAPIHowtheCodeWorks
HTMLJavaScript
CreatingYourOwnWebAPItoReferenceSynthesizerPatchDataTheDataStructure
HTMLCSS
HowtheCodeWorksBuildingontheAPIdata.jsmodule.js
ExtendtheJSONObjectSummary
25.TheFutureofJavaScriptandtheWebAudioAPITheWebAudioAPI1.0
3DSpacialPositioningRawModificationofAudioBufferDataSuggestionsforContinuedLearning
JavaScript6node.jsTheWebMIDIAPIOpenSoundControl
SummaryFurtherReadingBookWebsite
Index
Preface
Learningtoprogramcanbedaunting,andwewanttobethefirsttocongratulateyoufortakingonthechallenge!Second,wewanttothankyouforchoosingthisbook.
WhoIsThisBookFor?Thisbook is for anyonewho is involved in theworldof creative audio andwants to learn toprogram using the JavaScript language. There are many programming books directed towardartiststohelpthembuildwebsites,mobileapplications,games,andotherthings,butnexttononeisdirectedexclusively towardthesoundartscommunity.Thisbookisdesignedtofill thisroleandtoteachthefundamentalsofweb-basedsoftwaredevelopment,andspecifically,thebasicsoftheJavaScriptprogramminglanguagetosoundartists.
WhatThisBookIsNotThis book is not an audio technology reference. It does not take the time to explain thefundamentals of audio theory or sound engineering in depth.Words and phrases like dynamicrangecompression,convolutionreverb,andsampleratearethrownaroundlikecandywithonlyacursoryexplanation(iftheyareexplainedatall).Weassumethatyouareeitherfamiliarwithmanyofthesecoreaudioconceptsorknowenoughtofindtheanswersonyourown.Ifyouneedanaccommodatingaudiotechnologyreference,wesuggestDavidMilesHuber’sexcellentbookModernRecordingTechniques,Taylor&Francis.
ThisbookisalsonotdirectedtowardexperiencedprogrammerswhoaresimplyinterestedinJavaScriptortheWebAudioAPI.Ifthisdescribesyou,thenyoumayfindsomevaluehere,butyouarenottheintendedaudience.
HowtoLearntoProgramThefollowingareafewtipstohelpyougetthemostfromthisbook.
MakeConnectionsGenerally, it iseasier to learnnewthingsbymakingassociationsandconnections toareas thatyouarealreadyfamiliarwith.IfyouhaveeverprogrammedasynthoraMIDIsequencer,thenyouhavealreadydoneaformofprogramming.Thecontentsofthisbookaredesignedtobeabridgethatconnectsaworldyouare(presumably)familiarwith(soundandaudiotechnology)toatopicyouarelessfamiliarwith—JavaScriptandprogramming.Wesuggestthatyoutapintowhateverhasdrawnyoutosoundartwhilelearningthematerialinthisbook.
FlowandFrustrationAreNotOppositesIt’sveryimportanttoembraceasenseofflowwhenlearningtoprogram.Itisalsoimportanttoembrace frustration as part of the flow state and not as the antithesis of it. When you learnsomethingnew, theneurons inyourbrainaremakingconnections; thismayphysically feel likefrustration,butitjustmeansyourbrainisrewiring—literally.Embracethis.
MakeItHabitualProgramming isallabout learningabunchof little things thatcombine tomakebig things.Thebestway to learna lotof little things is through repetitionandhabit.Oneway todo this is tosimplyacceptprogrammingasanewpartofyourlifestyleanddoalittlebit(oralot)everyday.
BeCreativeandHavePersonalProjectsItisagoodideatohaveyourownpersonalprogrammingprojects.Themoreyouarepersonallyinvestedinaproject,themoreyouwilllearn.
TalkandTeachOneofthebestwaystovalidateyourownlearningis toteachsomeoneelse.Ifyoudon’thaveanyone to teach, thenyoucansubstitute thisbywriting tutorials.Thiswill forceyou tocollectyourthoughtsandexpressthemclearly.
KeepGoing
Ourfinalpieceofadviceistosimplystickwithit.Bestofluck!Ifyouhaveanyquestionsorcomments,youcanfindusat:http://www.javascriptforsoundartists.com
WilliamTurnerSteveLeonard
Acknowledgment
ThankstotechnicalassistantKeithOppel.
1OverviewandSetup
WhatIsaProgram?Aprogramisanysetofinstructionsthatiscreatedorfollowed.Inthisbook,wefocusonwritingcomputerprograms,whicharelistsofinstructionsthatacomputercarriesout.Theseinstructionscanbewritten and stored in various forms.Someof the firstmodern computers usedpunchedcards,switches,andcables.Earlyanalogmusicsynthesizerswereatypeofcomputerthatusedapatchbaystyleinterfacetomanuallyallowaprogrammertocreatespecificsounds.
WhatIsJavaScript?JavaScriptisamultipurposeprogramminglanguageinitiallycreatedtoaiddevelopersinaddingdynamicfeaturestowebsites.Thelanguagewasinitiallycreatedin11daysandreleasedin1995by a company called Netscape. Developed by Brendan Eich, its original release name wasLiveScript.WhenNetscape introducedsupport for the language in itsbrowser,LiveScriptwasrenamedJavaScript.AlthoughJavaScript issimilar innameto theJavaprogramminglanguage,they are completely unrelated. Today, JavaScript is used in everything from robotics to homeautomationsystems.
HTML,CSS,andJavaScriptThethreemaintechnologiesusedtobuildwebsitesandwebapplicationsareHTML,CSS,andJavaScript.
HTML stands for hypertext markup language and is the standard by which we createdocuments for the World Wide Web. You program HTML by writing elements (sometimesreferred to as tags for brevity). These elements contain text and other nested elements, whichmakeupthedocument’scontent.
CSSstandsforcascadingstylesheetsandisatoolusedtomodifyhowHTMLelementsandtext are presented. CSS is primarily a visual design tool. For example, with CSS you couldmodify an HTML element and give it an orange background, change its font size, place itverticallyorhorizontally,orperformanynumberofcreativevisualchanges.
JavaScriptisusedtoaddinteractiveresponsestouserinput.Everytimeauserclicks,scrolls,taps,moves themouse cursor, types, or performs an interactive event, JavaScript code canbetriggeredtochangethepageinsomemanner.TheJavaScriptlanguagewasinitiallydesignedtoperformthesefunctionswithinthecontextofdesigningwebsitesandapplications.
WhatIsaWebApplication?
A web application is any website that contains more than static, non-interactive pages. Thismeansthat,inawebapplication,thepageshavesomeinteractivecomponentsinadditiontothestatic text and images displayed. In the early days of the World Wide Web, websites werecomposed mostly of collections of static documents connected through highlighted text calledhyperlinks.Thesestaticpageshadnointeractionwithdatabases.Intheearly1990s,thisbegantochange, and web developers began creating websites that had features similar to desktopapplications that allowed users to interact with the page via form fields, buttons, and otherinteractivemeanstosenddataoverawebservertoandfromadatabase.
Early web applications were slow and limited by the technology of the time. In the early2000s,aculminationoftechnicalshiftsthatincludedclient-side-ratherthanserver-side-focusedwebapplicationshelpedmakewebapplicationsmoreresponsive.Partofthisshiftisattributableto a technology called AJAX (asynchronous JavaScript and XML). This technology pusheddynamicwebapplicationdevelopmentforwardbyallowingthebrowsertoretrieveandsenddatatoawebserverwithouthavingtoautomaticallyrefreshthepageintheprocess.AstheJinAJAXindicates, JavaScript is central to this technology, andwebapplicationsbegan to approach theinteractivespeedoftheirdesktopcounterparts.
As you might expect, within the audio world there were attempts to leverage this newtechnology, which resulted in browser-based audio players, editors, and musical instruments.Many of these applications were initially written using a technology called Flash. This is aproprietarytechnologythatrequiredtheusertodownloadandinstallanadditionalplug-intorunallprogramswritteninit.
In2008,anewerversionof theHTMLstandardwaswritten,calledHTML-5.Thisversionincludedanaudioplayer thatcoulddirectlystreamsound filesoffawebserverusingasinglelineofHTMLcode.Theplayeralsoincludedbuilt-in,user-facingcontrolsforplay,fast-forward,rewind,pause,stop,loop,andotheractions.However,forseriousaudiodevelopment,thiswasinadequate. Web application developers and audio aficionados wanted something more fullyfeatured.
WhatIstheWebAudioAPI?TheWebAudioAPIisaseriesofexposedcodepiecesthatyoucanusetoaccomplishmusicalandaudiotasksinawebbrowserwithlesseffortthanifyouweretocreatethemallfromscratch.The unexposed portion of theWeb Audio API lies in the web browser’s source code and iswritten inwhatever language theweb browser itself iswritten in. The technical core ofwebbrowsers is usuallywritten inmultiple lower-level languages,which can include (but are notlimitedto)C++,Java,andmachinelanguage.
TounderstandtheWebAudioAPI,youmustfirstunderstandwhatanAPIis.APIstandsforapplication programming interface. AnAPI is a portion of code that a programmer is givenaccessto,whichcontrolsalargerunseenbodyofcodewithincertainconstraints.Imagineif,inorder to learn how to play your favoritemusical instrument, you had to literally build it fromscratch.Asyoucan imagine, thiswouldgetvery tedious—especially if the instrumentwere tobreak. Thus, it’s much more convenient to learn to play a premade musical instrument. The
conveniencehere is that theconstructionprocess is removedandyouronlyconcern iswhat isimportant to you, which is the controls needed to use the instrument. In a similar manner,programmerswriteAPIsthatexposeonlysmallpiecesofcodefordeveloperstouse,andthesesmallpiecesofcodeallowyoutodoalotofworkwithminimaleffort.
Inadditiontobeingable to loadandplaybacksoundfiles, theWebAudioAPIalsoallowsyoutogeneratesoundfromscratchintheformofoscillators.Youcanthenmanipulateanysoundplayback or generation using filters, reverb effects, dynamic compressors, delay effects, and ahostofotheroptions.
SettingUpYourWorkEnvironmentTobeginworking,youmustfirstdeterminewhatbrowseryouaregoingtotroubleshootwith.Inreal-worldenvironments,youwoulduseatestsuitetotroubleshootamongdifferentbrowsersandplatforms.Inthisbook,wearegoingtokeepthingssimpleandonlyuseGoogleChrome.Thenextthingyouneedisacodeeditor.Fortheexercises,weassumeyouwillbeusingtheSublimeTexteditor.Technically,youcanuseanycodeeditoryouwant,butSublimeTextisofferedasafreetrialdownloadandisextremelypowerfulandwidelyused.Wethinkit’sworthyourinvestmentoftimetolearnit.
Thenextthingyouneedtodoiscreateafolderwithabasicworktemplate.
1. If you are not already using it, go to this URL to download and install Google Chrome:https://www.google.com/chrome/browser/desktop/.
2.Gotohttp://www.sublimetext.com/anddownloadandinstallSublimeText.
3.Createafolderonyourdesktoporinadirectoryandcallitwebaudiotemplate.
4.OpenSublimeText,and in thewindowthatappears, type thefollowingcode into it.Thensave the file (go to the Filemenu in SublimeText and clickSave As) as index.html andchooseyourwebaudiotemplatefolderasthedirectorytosaveitin.
<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>app</title><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"></head><!--__________________________________________BEGINAPP--><body>
</body><!--____________________________________________ENDAPP--></html>
5.Insidethewebaudiotemplatefolder,createanotherfoldercalledcss.
6.InSublimeText,createanewfilebygoingtotheFilemenuandclickNew.Savethisfileinyourcssfolderasapp.css.Leavethecontentsofthisfileempty.
7.Inthewebaudiotemplatefolder,createanotherfoldercalledjs.
8. Create a new empty document in Sublime Text, then type "use strict"; (includingquotations and semicolon) at the top of it and save it as app.js in the js folder you justcreated. This places your JavaScript in strict mode. Strict mode is a restrictive form ofJavaScriptthatenforcesbetterprogrammingpractices.AllJavaScriptcodeexamplesinthisbookwillassumeyouhavestrictmodeenabled.
You are now going to add a few extensions to SublimeText thatwillmakeworkingwith theeditoreasierinthelongterm.Todothis,youmustfirstdownloadandinstallthepackagemanagerplug-in. Go to the following link and follow the directions on the left side of the window:https://packagecontrol.io/installation.Whendone,closetheconsolebyenteringthekeys:Ctrl+`(apostrophe,onthekeywiththe~).
1. In the Sublime Text menu, go to Tools >Command Palette, and in the form field thatappears,typeinstall.YoushouldseeanoptionmenuappearthatsaysPackagecontrol:Installpackage.Clickthismenuoption.
2.Anotherformfieldwithaseriesofoptionsappears.Thisformfieldallowsyoutosearchandexplorevariousplug-insforSublimeText.Youarenowgoingtoinstallaplug-inthatallowsyoutocreatealocalwebserverthatwillbenecessarywhenworkingwithaudiofiles.Intheformfield,typeSublimeserver.Alistofsearchresultsshouldappear.Clickthefirstone. Look at the bottom of the Sublime Textwindow, and you should see “installing” insmall text.When this process is done, quit and restart Sublime Text.We will cover thespecificsof thewebserver ina laterchapter.Butrestassuredthat thissetupwillbe timewell spent. To verify that the plug-in is installed, go to Tools > SublimeServer > StartSublimeServer. Open your web browser to http://localhost:8080/, and it should displaySublimeServeratthetopofthepage.
3.Thislastplug-inyouaregoingtoinstallletsyouopenHTMLfilesinChromefromwithinSublimeText.ToinstalltheViewinBrowserplug-in,gotoTools>CommandPaletteandin the form field that appears, typeinstall. ClickPackage control: Install package.Then do a search forView inBrowser, and select the first option that appears.Once theinstallationisdone,youwillneedtogotothefollowingmenutosetuptheplug-intoworkwithChrome.
SetupViewinBrowserforWindowsInSublimeText,go to thePreferencesmenuandclickPackageSettings.Lookfor theView inBrowsermenuitem,hoveroverit,andselectSettings–Default.Selectallthecodeyouseeandcopyit.YouarenowgoingtopasteitintotheSettings–Userpageof thesameplug-in.SogobacktothePreferencesmenuandselectPackageSettings>ViewinBrowser>Settings–User.
Pasteallthecodeyoujustcopiedintothiswindow.Attheverybottom,youshouldseealineofcodethatsays“browser”:“firefox”.Changethewordfirefoxtoeitherchrome,orchrome64 ifyouhavea64-bitoperatingsystem.Itshouldlooklikethis:“browser”:“chrome”or“browser”:“chrome64”.IfyouopenanemptydocumentinSublimeTextandusethekeycommandCtrl+Alt+V,Chromeshouldlaunchandopenthatpage.
SetupViewinBrowserforMacAs soon as the plug-in is downloaded, you should be able to open an empty Sublime TextdocumentinChromeusingthekeycommandControl+Option+C.
HowtoCreateCodeSnippetsItcanbehelpfultoknowhowtocreatecodesnippetsthatyoucanaccesswithoutwritingthemoutcharacter-by-charactereverytime.Thankfully,SublimeTexthasafeaturethatallowsyoutodothiswithsnippets.Tocreateasnippet,dothefollowingsteps:
1.InSublimeTextmenu,gotoTools>NewSnippet.
2.Inthewindowthatappears,deleteeverythingonline3andpastethefollowingtext:Thisisatestsnippet.
3.Online6,removethe<!--and-->charactersandtypethewordtestinbetweenthetwoelements.Theresultshouldlooklikethis:<tabTrigger>test</tabTrigger>.
4.Savethefileinthedefaultdirectorythatappearsandcallittest.sublime-snippet.
5.Openyourindex.htmlfileinSublimeText,typethewordtest,thentaptheTABbuttononyourkeyboard.Thephrase"thisisatestsnippet"shouldappearintheeditor.
AccessingtheChromeDeveloperToolsGoogleChromehasabuilt-insuiteoftroubleshootingtoolscalledtheChromeDeveloperTools.Youcanaccessthesetoolsbyopeningthebrowserandusingthekeycommands:
WindowsOSorLinux:Ctrl+Shift+J
Mac:Command+Option+J
Wearenotgoingtogoovertheutilityofthedevelopertoolsjustyet,buttheywillbehighlightedthroughoutthebook.
TroubleshootingProblemsandGettingHelpIfyouhaveanytrouble,tryusingsearchenginestoresearchsolutions.Oneverygoodresourceishttp://stackoverflow.com,whichisacommunityofprogrammerswhoaskandanswerquestions.TheyhaveanicesectiononJavaScriptaswellasalivelyWebAudioAPIcommunitythatyoucanfindat:http://stackoverflow.com/questions/tagged/web-audio.
2GettingStartedwithJavaScriptandtheWebAudioAPI
HelloSoundProgramInanintroductiontoaprogramminglanguage,thefirstprogramyouwriteisoftencalled“HelloWorld,”which prints thewords “HelloWorld” on the screen. Becausewe are using theWebAudioAPItocreatesounds,thissectionexplainshowtocreatea“HelloSound”applicationthatimmediatelyplaysasoundwhenyourunit.
Copythefolderwebaudiotemplatefromthelastchaptertoanewdirectory,andrenamethecopytohello_sound.
Typethecodebelowintotheapp.jsfilethatispresentwithinthehello_soundfolder.Saveitandthenlaunchtheindex.html filefromyourwebbrowser.Youshouldhearabasicsinewaveoscillatorplaying.varaudioContext=newAudioContext();varosc=audioContext.createOscillator();osc.type="sine";osc.connect(audioContext.destination);osc.start(audioContext.currentTime);
AfteryouverifythattheHelloSoundprogramworks,closethebrowser.YoujustwroteyourfirstWebAudioAPIprogram!
Thecodeyoujustranisabasicoscillatorgenerationandplaybackscript.Thefirstlineinthescriptiscalledthe“AudioContext”andthistellsthebrowserthatyouareusingtheWebAudioAPI.Thenextlineofcodecreatesanoscillator.Thethirdlineofcodeassignsawaveformtypetothe oscillator, whereas line four connects the oscillator to a virtual audio output called thedestination, which is analogous to the speakers of your computer. The last line starts theoscillator playing.Wewill cover detailed operation of theWebAudioAPI in future chapters.First,though,weneedtocoverthebasicsoftheJavaScriptlanguage.
VariablesOneof the first steps inwritingaprogram isunderstandingvariablesandvariableassignment.Variablesarewordformsthatareusedtostoredata.Forexample:varwaveformType="sawtooth";
Thevariable here is namedwaveformType.This is precededby thevar keyword.Youalwaysspecifythevarkeywordpriortodeclaringthevariable.Declaringavariablemeansyouarecreatinganewvariable.Afterthevarkeyword,youtypeaspaceandgiveanametoyourvariable.Variablenamesaretypicallyareflectionofsomethingtheyrepresent.Inthiscase, thevariable is being used to describe a type of oscillator waveform and so is namedwaveformType. You probably noticed the odd capitalization of the word “type” inwaveformType. The convention of capitalizing words to distinguish them within variablenamesiscalledcamelcase.Thisconventionisusedbecausevariablenamescannotcontainwhitespacetoseparatethem.Ifyourewrotethevariableinthefollowingmanner,yougetanerror:varwaveformtype="sawtooth";//____returnsanerror
Type theabovecode into theapp.js fileofyourhello_sound template.LaunchChromeandopenthedevelopertools(Windows:Ctrl+Shift+JorMac:Command+Option+J).Insidetheconsoletab,youshouldseeanerrorsimilartotheoneinthefollowingimage.
Thetextingrayistheactualerrorandisidentifiedasasyntaxerror.Totherightoftheerror,youcanseethefileandthelinenumberwheretheerroroccurred.Thisnumbercorrespondstothelinenumber inyour file,whichmightdiffer fromtheone in the image.Afteryousee theerror,removethelineyouaddedthatiscausingtheerrorinapp.jsandsavethefile.
Afteryoudeclareandnameavariable,youcanassignsomedatatoit.Youusetheassignmentoperator“=”todothis.
ItisimportanttounderstandthatinJavaScriptthe“=”symbolisnotcalledtheequalsignanditsfunctionalitydoesnotmeanequalto.The“=”symbolindicatesassignment,soitiscalledtheassignmentoperator.Thevalueontherightsideoftheassignmentoperatorcontainsthedatayouwant to assign to the variable name on the left side. In the following example, the string"sawtooth"isassignedtothevariablewaveformType.varwaveformType="sawtooth";
When you assign a string of words to a variable, you must place them between quotationmarks.Theresultingdatatypeiscalledastring.Datatypesrepresentthetypesofdatathatyou
canuseinyourprogram.Differentprogramminglanguageshavedifferentdatatypes.JavaScripthassixdatatypes,andoneoftheseisthestringdatatype(seeChapter6foralistofJavaScriptdatatypes).
Afteryouassigndatatoyourvariable,youmustendthevariabledeclarationwithasemicolon.Insummary,therearefivepartstoavariabledeclaration:
■Thevarkeyword
■Thevariablename
■Theassignmentoperator
■Thedatayouwishtoassigntothevariable
■Theclosingsemicolon
Youcanassignmultiplevariablesatonceusingthefollowingsyntax:varosc1=1200,osc2=1300,osc3=100;
In some cases, you might want to declare a variable and not assign data to it, as in thefollowingexample:varwaveformType;
If you do this, JavaScript automatically assigns undefined to it. You can also assignundefinedexplicitlylikethis:
varwaveformType=undefined;
Thekeywordundefined is another JavaScriptdata type.Notice thatundefined is notenclosedinquotationmarksbecauseitisnotastringbutrepresentsadatatype.
nullThe primitive valuenull is similar to the primitive valueundefined. Both can act as aplaceholderforemptyvariables.Whenthetypeofoperator(discussedlaterinthischapter)isusedtodeterminethetypeofnull,theresultisobject.Thisisnotwhatyoumightexpectandisaflawinthelanguage.Thecorrectreturnedvalueshouldbenull.Becauseofthis,wesuggestthatyouneverusenullandalwaysuseundefined.
DocumentingYourCodewithComments
Whenyouareprogramming,itisagoodhabittotypemessagesintoyourcodethatareintendedtobe read by human beings (yourself or others) and not be interpreted by the computer. Thesemessagesarecalledcomments.Youcanwrite either single-lineormultiline comments inyourprogram,andtheylooklikethis://Thisisasinglelinecomment.//Itbeginswithtwoforwardslashcharacters//Theseendattheendoftheline/*Thisisamulti-linecommentandbeginswithaforwardslashandasterisk.Itendswithanasteriskandaforwardslash*/
Inareal-worldscenario,wemightcommentourcodelikethis:varwaveformType="sawtooth";//oscillatorvariable
Allthecharactersfromthe//totheendofthelineareignoredbythecomputer.
ExploringVariableswithanOscillatorNowthatyouunderstandwhatvariablesare,thefollowingexampleshowshowyouusethem.
Open up the code you wrote at the beginning of this chapter, and add the variablewaveformTypetoit,asinthefollowingcode:varaudioContext=newAudioContext();varwaveformType="sawtooth";//___addedvariablevarosc=audioContext.createOscillator();osc.type="sine";osc.connect(audioContext.destination);osc.start(audioContext.currentTime);
Replacetheosc.typeassignmentwiththewaveformTypevariablelikethis:varaudioContext=newAudioContext();varwaveformType="sawtooth";//___addedvariablevarosc=audioContext.createOscillator();osc.type=waveformType;//__Assignedittoouroscillatortypeosc.connect(audioContext.destination);osc.start(audioContext.currentTime);
Launchyourwebbrowser,andinsteadofhearingasinewaveform,youshouldhearasawtoothwaveform.
In this example, the following declarations assign values to variables that represent otherwaveformtypes.varaudioContext=newAudioContext();//___4variablesthatrepresentoscillatorwaveformsvarsaw="sawtooth";varsine="sine";vartri="triangle";varsquare="square";//___AvariableintendedtocontainoneofthesewaveformsvarcurrentWaveform=undefined;currentWaveform=square;
//_____________________________Startofoscillatorvarosc=audioContext.createOscillator();osc.type=currentWaveform;//Assignedittoouroscillatortypeosc.connect(audioContext.destination);osc.start(audioContext.currentTime);
Eachofthefournewvariablescontainsastringthatrepresentsanoscillatorwaveformtype.ThesquarevariableisassignedtothecurrentWaveformvariableinthefollowingline:
currentWaveform=square;
Notice thatnonewdeclaration is required for thecurrentWaveform variable to assign(andreplace)whateverwaspreviouslyassignedtoit.Thenewdataontherightsideof“=” isassignedtocurrentWaveform.Ifyoulaunchyourwebbrowser,youwillhearasquarewaveplay. In programming, being able to overwrite variables in this manner is referred to asmutability(changeability),andwesaythatvariablesaremutable.Theoppositeofthisiscalledimmutability.
console.log()Whenprogramsbegintogetbig,itcanbedifficulttoknowwhatvalueisassignedtoavariableatany given moment. One way you can find out is by using a built-in feature calledconsole.log().
Thewayyoudo this isby typingconsole.log() intoyourcodeat thepointwhereyouwant to check a given variable’s assignment. You then place the variable name inside theparentheses.
ToseewhatthecurrentWaveformvariablehasasitsassignment,youdothis:varaudioContext=newAudioContext();//Added4variablesthatrepresentoscillatorwaveformsvarsaw="sawtooth";varsine="sine";vartri="triangle";varsquare="square";varcurrentWaveform=undefined;currentWaveform=square;console.log(currentWaveform);//___square//____________________________________Startofoscillatorvarosc=audioContext.createOscillator();osc.type=currentWaveform;//Assignedittoouroscillatortypeosc.connect(audioContext.destination);osc.start(audioContext.currentTime);
LaunchChrome,openthedevelopertoolsandclicktheconsoletab;youwillseetheoutputofourconsole.log().
Onethingtorememberis thatbecausevariablescanhavedifferentvaluesatdifferent times,theoutputofconsole.log()dependsonwhereitisplacedintheprogram.Ifyoumodifythelast example and place console.log() immediately after the currentWaveformvariable,whichhasundefinedassignedtoit,thenundefinedisoutputtothelog.
//__________AvariableintendedtocontainoneofthesewaveformsvarcurrentWaveform=undefined;console.log(currentWaveform);//______resultsin"undefined"currentWaveform=square;
Sofarwe’vementionedthreeofthesixdatatypesinJavaScript.Thefirstwasstring, thesecondwasundefined,andthelastwasnull.
Beforewegofurther,let’sexplorethestringdatatypeabitmoreindepth.
StringAs we already discovered, strings are denoted by quotation marks. The variable below is astring:varoscillator="square";
You can manipulate strings in different ways. One of the most common is by combiningmultiplestringsintoonestring.Thisiscalledconcatenation,anditworksbyusingtheplussign(+)likethis:varoscillator="saw"+"tooth";console.log(oscillator);//sawtooth
Hereisanotherexampleofconcatenatingtwovariablesandstoringtheminanewvariable.varphrase="Thissoundisan";varsoundType="oscillator";varsentence=phrase+soundType;console.log(sentence);//"Thissoundisanoscillator".
Noticethatstringscancontainwhitespace.Thisisaperfectlyvalidstring,eventhoughitcontainsalotofwhite-spacecharacters:
varmyFavoriteSynthCompany="MyfavoritesynthcompanyisMoog";
Ifyouwanttogetthenumberofcharactersinastring,youcanusewhatiscalledthelengthpropertylikethis:console.log(myFavoriteSynthCompany.length);//33
Theoutputofthelengthpropertyincludesthewhite-spacecharactersofthestring.
Built-InStringMethodsJavaScripthasaseriesofbuilt-intoolscalledmethodsthatallowyoutomanipulatedata.Someofthesemethodsarespecificallydesignedtomanipulatestringdata.
Thesearecalledstringmethods.Toseehowtouseastringmethod,takealookattheexamplesofthetoUpperCase()and
toLowerCase()methods.
toUpperCase()Thismethodchangesallthecharactersinastringtouppercase.varoscillator="sawtooth";oscillator.toUpperCase();//SAWTOOTH
toLowerCase()Thismethodchangesallthecharactersinastringtolowercase.varoscillator="SAWTOOTH";oscillator.toLowerCase();//"sawtooth"
Someusefulstringmethodsare:
charAt() Returnsacharacteratanygivenindexinastring
replace() Findsandreplacesagroupofcharactersinastring
slice() Extractspartofastring
Youdonotneedtoimmediatelymemorizehoweachofthesemethodsworks,butit’sagoodideatoknowaboutthem.Thisway,whenyoudoneedtoimplementanyofthefunctionalitiestheyprovide,youknowwhichtooltoreachfor.Ifyouwouldliketoexploremorestringmethods,agood resource is the Mozilla Developer Network at: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String.
Let’sgothrougheachoneoftheseandexplainhowtousethem.
charAt()Thismethodgetsacharacteratanygivenindexvaluewithinastring.Forexample,ifyouhavethestring"oscillator-1"andwanttoknowwhatthesecondletterofthisstringiswithoutactuallylookingatit,youcandothis:varsound="oscillator";console.log(sound.charAt(1));//"s"
NowyoumightbewonderingwhycharAt(1)returns“s”andnot"o".Thereasonisthatthecountbeginsatzero.So,togetthefirstletterdothis:console.log(sound.charAt(0));//"o"
Whenanindexlistbeginswithzero,itiscalledazero-basedindex.
replace()
Thismethodfindsagroupofcharactersinastringandreplacesthemwithanotherstring.Ifyouwanttoreplaceanentireword,youcandoitlikethis:varmyFavoriteSynthCompany="MyfavoritesynthcompanyisMoog.Moogisgreat!";varmyNewFavoriteSynthCompany=myFavoriteSynthCompany.replace("Moog","DaveSmithInstruments");console.log(myNewFavoriteSynthCompany);/*MyfavoritesynthcompanyisDaveSmithInstruments.Moogisgreat!*/
Asyouprobablynoticed,whenusing the replacemethod in thismanner itonly replaces thefirst instanceof thewordyouselect.To replaceall instancesof theword,youneed touse thefollowingsyntaxtogloballyreplacetheminthestring.varmyFavoriteSynthCompany="MyfavoritesynthcompanyisMoog.Moogisgreat!";varmyNewFavoriteSynthCompany=myFavoriteSynthCompany.replace(/Moog/gi,"DaveSmithInstruments");console.log(myNewFavoriteSynthCompany);/*MyfavoritesynthcompanyisDaveSmithInstruments.DaveSmithInstrumentsisgreat!*/
The g stands for global and the i denotes case insensitivity. If you want the stringreplacement to be case sensitive, you use ag and omit thei. These characters are part of apattern-matchinglanguageforstringdatacalledregularexpressions.Regularexpressionsareanadvancedtopicthatwillnotbecoveredfurtherinthisbook.
slice()Thismethodextractspartofastring.varoscillator="sawtooth";varsound=oscillator.slice(0,3);console.log(sound);//saw
LikecharAt(),slice()worksonazero-basedindex.Thismeansthefirstcharacterisalways zero.The slicemethod takes twovalues: a beginning indexvalue and an ending indexvalue.Whenamethodtakesvalues,theyarecalledarguments.ThecharAt()methodtakesoneargument.Theslice()methodtakestwoarguments.Theslicemethod’sfirstargumentiswheretheslicestarts,andthisvalueisincludedintheslice.Thesecondvalueiswherethesliceendsandisnoninclusive.Thismeansallthecharactersupto,butnotincluding,thesecondvalueareincludedintheslice.
ThelengthPropertyThelengthpropertyisanadditionaltoolthatallowsyoutogetthenumberofcharactersinastring.Apropertylookssimilartoamethodbutdoesnotincludeparenthesesanddoesnotrequireargumentstoreturnavalue.Thecharactercountofthelengthpropertystartsatone,notzero.
varinstrument="piano";console.log(instrument.length);//5
Ifyouwanttogetthelastvalueofastring,youcancombinethelengthpropertywith thecharAt()method.This allowsyou to retrieve the last character in a string in amanner thatdoesn’t require you to know how long the string is. The code shows an example of this. Thereason you subtract 1 from the length property is because the length property beginscountingatone,whereascharAt()beginscountingatzero.Therefore,yousubtract1fromthelengthpropertytocompensatefortheoffset.
varsound="oscillator-1";varoscNumber=sound.charAt(sound.length-1);console.log(oscNumber);//1
NumbersInJavaScript,numbersareadistinctdatatype.BelowisavariablenamedfrequencyValue,andit isassignedanumberof200.It is thenassignedtotheoscillator’spitch.IfyouplacethecodebelowinanewJavaScriptfileandrunit,youwillhearanoscillatorplayatafrequencyof200Hz.Modify thenumbervalueassigned to thefrequencyValuevariableand launch thecodetoheartheoscillatorplayatdifferentpitches.varaudioContext=newAudioContext();varfrequencyValue=200;//___CreatevariablefrequencyValuevarwaveform="sawtooth";varosc=audioContext.createOscillator();osc.type=waveform;//_____assignittotheoscillatorspitchosc.frequency.value=frequencyValue;osc.connect(audioContext.destination);osc.start(audioContext.currentTime);
HowtoDeterminetheDataTypeofaVariableYoucandiscernthedifferencebetweendatatypesinvariablesbyusingthetypeofoperator.
varwaveform="sine";varpolyphony=16;console.log(typeofwaveform);//stringconsole.log(typeofpolyphony);//number
Unlike strings, numbers do not use quotation marks. In fact, if you did use a number withquotationmarks,itsdatatypewouldnotbenumber,itwouldbestring.
Here’sanexample:varoscillators="6";varpolyphony=6;console.log(typeofoscillators);//stringconsole.log(typeofpolyphony);//number
Youcandobasicmathwithnumbersusingthefollowingsymbols.Thesesymbolsarecalledarithmeticoperators.
+ Addition
− Subtraction
* Multiplication
/ Division
% Modulo
ExamplesofArithmeticOperatorsconsole.log(5+5);//10console.log(10-5);//5console.log(5*5);//25console.log(25/5);//5console.log(10%9);//1
Thelastsymbol(%)mightbenewtoyou,andit ispronouncedmoj-uh-loh.Thepurposeofthissymbolistooutputtheremainderofadivision.So,forexample:console.log(12%9);//Thisequals3
The precedent rules of algebra also apply. If you wrap a calculation in parentheses, thecalculationinsidetheparenthesesisperformedfirst.
ExamplesofPrecedencevaroscillator1=1000;varoscillator2=100;varoscillator3=20;varcombinedOscillator=oscillator1+(oscillator2*oscillator3);console.log(combinedOscillator);//3000
Ifyouwanttodomoreelaboratecalculations,JavaScripthasabuilt-intoolcalledtheMathobject,whichallowsyoutouseacollectionofmathmethodstomanipulatenumbers.
So, for example, if youwant to round a decimal number to its nearest integer, you can useMath.round()likethis:Math.round(1000.789);//outputs1001
Someusefulmathobjectmethodsare:
Let’sgoovereachof theseonebyone. Ifyouwould like toexploremoremathmethods, agood site is the Mozilla Developer Network at: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math.
Math.min()andMath.max()Math.min() finds the smallest number in a collection of numbers, whereasMath.max()allowsyoutofindthelargestnumberinacollectionofnumbers.Math.min(5000,2000,80);//80Math.max(5000,2000,80);//5000//___________________________Withvariablesvarfreq_1=5000;varfreq_2=2000;varfreq_3=80;Math.min(freq_1,freq_2,freq_3);//80Math.max(freq_1,freq_2,freq_3);//5000
Math.ceil()andMath.floor()Thesetwomethodsturnadecimalnumberintoaninteger.Math.ceil()roundsuptothenexthigher integer value if there are any nonzero digits to the right of the decimal, whereasMath.floor()keepstheintegervalueafterdiscardingthedigitstotherightofthedecimal.Math.ceil(3.00333);//4Math.floor(3.9999);//3
Math.random()Therandommethodcreatesarandomnumberbetweenzeroandone.varrandomNumber=Math.random();console.log(randomNumber);//example:0.019790495047345757
You can combine Math.random() with Math.floor() to create a random numberbetweentwovalues.Theexpressioninthefollowingexamplecreatesarandomintegerbetween20and20,000.
varmax=20000;varmin=20;varrandomInteger=Math.floor(Math.random()*(max-min+1)+min);console.log(randomInteger);//Between20and20000
Math.abs()Theabsmethodallowsyoutogettheabsolutevalueofanumber.varnum=Math.abs(-100);console.log(num);//100
Thisisusefulforfindingthedifferencebetweennumericvariablesofunknownvalues.vara=1000;varb=5000;console.log(Math.abs(b-a));//4000
Number-to-StringConversionIf you want to convert between numbers and numeric strings, you can use the followingtechniques.
Toconvertastringtoanumber,placetheplussymbol(+)beforethestringlikethis:varnumericString="120";varnum=+numericString;//plussymbolconsole.log(num);//120console.log(typeofnum);//number
If youwant to convert a number to a numeric string, concatenate thenumberwith an emptystringlikethis:varnum=80;varnumericString=num+"";console.log(numericString);//80console.log(typeofnumericString);//string
Ifyouattempttodoamathoperationusingnonnumericvalues,sometimesyouwillreceiveareturnedvalueofNaN.Thisstandsfornotanumber.Hereisanexampleofattemptingtoaddtwovaluesinwhichonevalueisanumberandtheotherisnot.varosc1=undefined;varosc2=200;console.log(osc1+osc2);//NaN
Arrays
Arraysareaconstructthatholdsmultiplepiecesofdata.Youcanthinkofthemasvariablesthatholdmorethanoneitem.Arraysareexpressedusingbrackets,whereeachitemisseparatedbyacomma.Eachiteminthearrayisdesignatedanindexnumberwiththefirstitemstartingatzero.varwaveforms=[];//emptyarrayvarwaveforms=["square","sawtooth","triangle","sine"];//arraywithsomedata
Ifyouwanttoaccessanyofthesedata,youcanusethefollowingnotation:waveforms[0];//squarewaveforms[1];//sawtoothwaveforms[2];//trianglewaveforms[3];//sinewaveforms[4];//undefined(nodata)
Ifyouwanttoknowhowmanyitemsareinsideanarray,usethelengthpropertylikethis:
varwaveforms=["square","sawtooth","triangle","sine"];waveforms.length;//4
Arrayscomewithbuilt-inmethodsthatyoucanusetomanipulatethedatainthem.Afulllistofthese are available at the Mozilla Developer Network at this URL:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array. Weareonlygoingtogooverahandfuloftheseandtheyare:
push() Addsadditionalitemstotheendofanarray
pop() Removesasingleitemfromtheendofanarray
shift() Removesasingleitemfromthebeginningofanarray
unshift() Addsadditionalitemstothebeginningofanarray
concat() Concatenatesarraystogetherintoonearray
push()Thismethodaddsitemstotheendofanarray.varsynthFrequencies=[5000,1000,500];synthFrequencies.push(100);/*Thisplacesanewitemattheendofthearray*/console.log(synthFrequencies);//[5000,1000,500,100]
Youcanusethepushmethodtoaddmultipleitemsatonce.varsynthFrequencies=[5000,1000,500];synthFrequencies.push(100,50,30);console.log(synthFrequencies);//[5000,1000,500,100,50,30]
pop()Thismethodremovesasingleitemattheendofanarray.varsynthFrequencies=[5000,1000,500];synthFrequencies.pop();console.log(synthFrequencies);//[5000,1000]
Ifyouwanttocapturethelastitemyouremovedfromanarrayinavariable,dothis:varsynthFrequencies=[5000,1000,500];varlastItem=synthFrequencies.pop();console.log(lastItem);//500
shift()Thismethodremovesanitemfromthebeginningofanarray.varsynthFrequencies=[5000,1000,500];synthFrequencies.shift();console.log(synthFrequencies);//[1000,500]
Ifyouwanttocapturethefirstitemyouremovedfromanarrayinavariable,dothis:varsynthFrequencies=[5000,1000,500];varfirstItem=synthFrequencies.shift();console.log(firstItem);//5000
unshift()Thismethodaddsnewitemstothebeginningofanarray.varsynthFrequencies=[5000,1000,500];synthFrequencies.unshift(7500,6000);console.log(synthFrequencies);//[7500,6000,5000,1000,500]
concat()Thismethodmergesmultiplearraystogetherintoonearray.vardrumMachines=["MPC","Machine","TR808"];varkeyboards=["Juno","ARP","Jupiter"];varpercussion=["vibraphone","bongos"];varstringed=["guitar","bass","harp"];varinstruments=drumMachines.concat(keyboards,percussion,stringed);console.log(instruments);/*['MPC','Machine','TR808','Juno','ARP','Jupiter','vibraphone','bongos','guitar','bass','harp']*/
SummaryInthischapter,youlearnedaboutvariables,comments,numbers,strings,andarrays.Inthenextchapter,youwilllearnaboutvariousassignmentandlogicaloperators.
3Operators
Youlearnedaboutthebasicassignmentoperator(=)andsomeofthearithmeticoperatorsinthepreviouschapter.Inthischapter,wearegoingtoexploreotherassignmentoperators,aswellascomparisonoperators,thatallowyoutodeterminetherelationshipbetweenvariablesandvalues,suchaswhethertheyhavethesamevalue.WewillalsoexploretheBooleandatatype,whichhaseithera trueora falsevalue thatcanbeassigned tovariablesor is the resultofacomparisonoperation.
WhatAreOperators?Operatorsrepresentactionsthatyouusetochangethevalueofavariable,orcomparevaluesorvariables.Thewordoperand isused todescribeavaluebeingused inanoperation involvingoperators. So in the following example, the operands are 300 and 400. The output of thecomparisonissaidtobewhattheexpressionevaluatesto.Inthefollowingexample,theoperationevaluatestofalse.300==400/*Thevalueshere(300and400)arecalledoperands,andtheoutputevaluatestofalse.*/
Operatorsfallintoarithmetic,assignment,orlogicalcategories.Thearithmeticoperatorsthatwecoveredinthepreviouschapterareusedwithnumbers.Theassignmentoperatorsareusedtoassignvalues tovariables.The logicaloperators areused to compare twovalues and returnatrueorfalsevaluebasedontheresultofthecomparison.
AssignmentOperatorsAssignmentoperatorsareusedtoassigndatatovariables.Hereisalistofassignmentoperators:
AssignmentThisoperatorassignsavaluetoavariable.varosc=100;
Withassignmentoperators,youcanalsoassignvariablestoothervariables.varosc1=100;varosc2=osc1;console.log(osc2);//100
AdditionAssignmentThisoperator incrementsanumericvariableorappendsastringtoavariable.Inthefollowingexample,anoscillatorisassignedavalueof100andthenincrementedby100togiveitavalueof200.varosc=100;osc+=100;console.log(osc);//200
Todemonstratetheuseoftheadditionassignmentoperator, thefollowingcodesetsanever-increasing frequency change to an oscillator and you can listen to the effect.Amethod calledsetInterval()isdefined,althoughthespecificsofsetInterval()arenotimportantatthistime.Whatisimportantisunderstandingthattheadditionassignmentoperatorisincrementingthefrequencyvalueby100every0.5secondswhensetInterval()iscalled.varaudioContext=newAudioContext();varosc=audioContext.createOscillator();osc.frequency.value=300;osc.connect(audioContext.destination);osc.start(audioContext.currentTime);setInterval(function(){osc.frequency.value+=100;/*____Incrementfrequencyvalueby100every0.5seconds*/console.log(osc.frequency.value);//_____Viewchange},500);//________________________500millisecondsis0.5seconds
When you use the addition assignment operator with a string, the string you supply isconcatenatedwiththevariable.Hereisanexample:varkeyboards="";keyboards+="Korg";keyboards+="Yamaha";keyboards+="Kurzweil";console.log(keyboards);//KorgYamahaKurzweil
SubtractionAssignmentThisoperatorisusedtodecrementanumericvariable.varosc=500;osc-=100;console.log(osc);//400
MultiplicationAssignmentThisoperatormultipliesavariablewithavalueandassignsittothevariable.varosc=200;osc*=2;osc*=2;console.log(osc);//800
DivisionAssignmentThisoperatordividesavariablebyavalueandassignsittothevariable.varosc=200;osc/=2;osc/=2;console.log(osc);//50
ModuloAssignmentThis operator divides a variable by a value and assigns the remainder of that division to thevariable.varosc=200;osc%=150;console.log(osc);//50
TheBooleanDataType
TheBooleandata typeiseither trueorfalse.This isconveyedbytheword-formvaluestrueandfalse.Booleansareimportantbecauseyoucanusethemtoprogramonoroff(trueorfalse)valuesintothecode.So,forexample,youcouldusethemasavaluethattogglesanoscillatoronoroff.AssigningaBooleanvaluetoavariableinJavaScriptlookslikethis:varoscillatorIsOn=true;//trueoscillatorIsOn=false;//changedtofalse
Booleanvaluescanalsobetheresultofthecomparisonoperatorsdescribedbeloworusedinconditionalsstatements,whichwewillcoverinthenextchapter.
ComparisonOperatorsComparison operators are used to compare two variables or values. They output atrue orfalse value depending onwhether the variables or values are similar or different from oneanother insomeway.Thesimilarityordifferencebeingtestedfor isdependentontheoperatorused. So, for example, if you test whether two values are the same using the strict equalityoperator (= = =) and they are not the same, the resulting value is false. There are eightcomparisonoperators.
EqualityOperatorThis operator checks whether the left operand is equal to the right operand. It then returns aBooleanvaluetorepresenttheoutcomeofthecomparison.200==200;//true"200hz"=="200hz";//truevarosc1="200hz";varosc2="200hz";console.log(osc1==osc2);//true
Theequalityoperatorcanbeabittrickybecauseitattemptstodoadatatypecoercionbeforecomparingoperands.Datatypecoercionoccurswhenthecodeinterpreter(inourcasethewebbrowser)attemptstoconvertonedatatypeintoanother.Inthefollowingexample,wecomparea
numberandanumericstring.JavaScripttriestoconvertthestringtoanumberbeforedoingthecomparison.Ifthestringisanumericstring,theconversionissuccessful,andthecomparisonisperformed. In this case, the result of the comparison is the Boolean valuetrue because thenumeric string “200” is successfully converted to the value 200, which matches the value ofosc1.varosc1=200;varosc2="200";console.log(osc1==osc2);//true
Ifanonnumericstringiscomparedagainstanumber,theresultisalwaysfalse.200=="oscillator"//false
StrictEqualityOperatorToprotectagainsttheconfusionoftypecoercionusingtheequalityoperator,youcanusethestrictequalityoperator.Thisoperatordoesnotdodatatypecoercion.Thismeansthat,ifanynumericstring is compared against a number, the result is always false. For newer JavaScriptprogrammers,wesuggestthatyoualwaysusethisoperator.Restrictingyourselftothisoperatorhelpstomitigateproblemsinvolvingcoercionbeforetheystart.//_________________________Examples900===900//truevarosc1=200;varosc2="200";console.log(osc1===osc2);//false
GreaterThanandLessThanOperatorsTheseoperatorsproduceaBooleanresultthatisbasedonwhethertheleftoperandislessthanorgreaterthantherightoperand.100<200//true300<200//false300>200//true300>500//false
Thegreaterthanandlessthanoperatorsdodatatypecoercionasshowninthisexample:600>"500"//true600<"500"//false
GreaterThanorEqualtoOperatorThisoperatorreturnsaBooleanvalueoftrueifthefirstoperandisgreaterthanorequaltothesecondoperand.
varosc1=300;varosc2=500;varosc3=300;osc3>=osc1//trueosc2>=osc1//trueosc1>=osc2//false
Thegreaterthanorequaltooperatordoesdatatypecoercionasshowninthisexample:300>="300"//true
LessThanorEqualtoOperatorThisoperatorreturnsaBooleanvalueoftrueiftheleftoperandislessthanorequaltotherightoperand.300<=300//true300<=500//true300<=200//false
Thelessthanorequaltooperatordoesdatatypecoercionasshownintheseexamples:300<="300"//true300<="500"//true300<="200"//false
NotEqualtoOperatorThis operator is a combination of the NOT symbol and the equal sign. The NOT symbol isexpressedasanexclamationmarkandissometimesreferredtoasthebangoperator.WhenNOTis coupledwith an equal sign to produce the not equal to operator, it can be used to return aBooleanvaluethatisbasedonwhethertwovaluesarenotequaltoeachother.Ifthetwovaluesarenotequal,theresultistrue.Ifthetwovaluesareequal,theresultisfalse.300!=200//true300!=300//false
Thenotequaltooperatordoesdatatypecoercionasshowninthisexample:"300"!=300//false
StrictNotEqualtoOperatorThisoperatorreturnsaBooleanvaluethatisbasedonwhethertwovaluesarenotequaltoeachother.Thestrictnotequaltooperator,unlikethenotequaltooperator,doesnotdotypecoercion."300"!==300//true300!==300//false
LogicalOperatorsLogicaloperators allowyou tocheck if a collectionof statements is trueor falseand returnaBooleanvaluebasedonthisinformation.
LogicalOperator Name&& AND|| OR! NOT
TheLogicalANDOperatorThelogicalANDoperatorevaluatestotrueonlyifalltheoperandsaretrue.Thewayitworksisthatfirst, thevalueontherightsideoftheoperatorisevaluated,andifitsvalueisfalse,theBooleanvalueoffalse is returned. In this case, thevalueon the left sideof theoperator isneverconsidered!
Ifthevalueontherightsideoftheoperatorevaluatestotrue,thenandonlythendoestheANDoperator check the value on the left side of the operator. If the value on the left side of theoperatorisfalse,thentheBooleanvaluefalseisreturned.InthecasewhereboththevaluesontheleftandrightsidesofthelogicalANDoperatoraretrue,theBooleanvaluetrueisreturned.true&&true//truetrue&&false//falsefalse&&true//falsefalse&&false//false
TheLogicalOROperatorThisoperatorreturnstrueaslongaseitheroftheoperandsistrue.true||true//truetrue||false//truefalse||true//truefalse||false//false
TheNOTOperatorThisoperatorinvertsaBooleanvalue.!false//true!true//false
Anotherwaytolookatthiscodeisthat,ifavalueisnotfalse,thenitistrue,andifitsvalueisnottrue,thenitisfalse.
InJavaScript,therearesixvaluesthatevaluatetofalse.Theyarethefollowing:
false""nullundefined0NaN
Allothervaluesevaluatetotrue.When you specify the NOT operator twice in a row before a variable or an operand, the
resultantvalueisitsoriginalBooleanvalue.!!false//false!!true//true!!0//false!!""//false!!null//false!!undefined//false!!NaN//false
SummaryInthischapter,youlearnedaboutJavaScriptassignmentandlogicaloperators,theBooleandatatype,andwhatvaluesevaluate tofalse. In thenextchapter,youwill learn to leverage thesetoolsusingtwonewconcepts:conditionalsandloops.
4ConditionalStatementsandLoops
Conditional statements and loops are two of themostwidely used constructs in programming.Conditionalstatementsallowyourprogramtomakechoicesbasedonasetofcriteria.Loopsuserepetition,allowingyourprogramtocompletemanytasksquickly.
ConditionalStatementsTocreateprogramsthatdomorethanbasiccalculationsorprinttext,theymustbeabletomakedecisions. You can program these decisions by using conditional statements. Conditionalstatements check if a value is true or false and then execute a branch of code based on thiscondition.Wearegoingtogooverthefollowingthreeconditionalstatements:
■if
■switch
■ternary
TheifStatementThesyntaxofanifstatementconsistsoftheifkeyword,apairofparentheses,andtwocurlybraces.Thisiswhatanemptyifstatementlookslike:
if(){}
Touseanifstatement,youplaceavalueorconditioninsidetheparenthesesandsomecodeto execute inside the curlybraces. If the condition inside theparentheses evaluates to true, thecodeinsidethecurlybracesisexecuted.Iftheconditionevaluatestofalse,noactionistakenandthe code inside the curly braces is skipped. In the following code, anif statement is used to
checkifanoscillatorfrequencyissetto80Hzpriortoplaystart.Ifitis,theoscillatorplays;ifitisnot,thecodeinsidethecurlybracesisignoredandtheoscillatordoesnotplay.//___________________________________BEGINSetupvaraudioContext=newAudioContext();varosc=audioContext.createOscillator();osc.type="sawtooth";osc.frequency.value=80;osc.connect(audioContext.destination);//___________________________________ENDSetup//___________________________________BEGINCheckfrequencyif(osc.frequency.value===80){osc.start(audioContext.currentTime);}//___________________________________ENDCheckfrequency
If statements can also have an optionalelse branch that executes if the initial conditionevaluatestofalse.Inthefollowingcode,theifstatementcheckstoseeiffrequency.valueis 100Hz. If this condition is true, the oscillator begins to play. If this condition is false, theelsebranchexecutes,assignsfrequency.valueto50Hz,andstartstheoscillatorplaying.//____________________________________BEGINSetupvaraudioContext=newAudioContext();varosc=audioContext.createOscillator();osc.type="sawtooth";osc.frequency.value=200;osc.connect(audioContext.destination);//____________________________________ENDSetup//____________________________________BEGINConditionalif(osc.frequency.value===100){//__evaluatestofalseosc.start(audioContext.currentTime);}else{//__Sothisplaysosc.frequency.value=50;osc.start(audioContext.currentTime);}//____________________________________ENDConditional
Supposeyouwanttocheckformorethantwoconditionsanddosomethingdifferentforeachone,youcandothisbycreatinganifstatementwithmultipleelseifbranchesinsequence.The finalelse statement catches all conditions that were not met along the way. An emptyexamplelookslikethis:if(){
}elseif(){
}else{
}
Inthefollowingworkingexample,thecodeexecutesandcheckstoseeifosc.typeissetto"sine". If this condition evaluates to false, the else if branch runs and checks if theoscillatortypeissetto"sawtooth".Thisevaluatestotrue,andtheoscillatorstartsplaying.If
osc.typeisnotsetto"sine"or"sawtooth"(inotherwords,ifbothconditionsevaluatedtofalse),thentheresultisexecutionofconsole.log(),whichoutputs“noconditionmet.”varaudioContext=newAudioContext();varosc=audioContext.createOscillator();osc.type="sawtooth";osc.connect(audioContext.destination);if(osc.type==="sine"){osc.start(audioContext.currentTime);}elseif(osc.type==="sawtooth"){osc.frequency.value=50;osc.start(audioContext.currentTime);}else{console.log("noconditionmet");}
TheswitchStatementIf you catch yourself writing an if statement with a lot of else if branches, you shouldconsiderusingaswitchstatement.Aswitchstatementallowsyoutocheckifavariablehasaparticular value assigned to it and then runs a block of code that begins where that value isdefined.The followingcode is an exampleof an emptyswitch statement.The expression inparenthesesdeterminesavalue.Thecase statementsdefinevalues thatyouwant tocatchandthenrunsomecode.Eachcasestatementisterminatedbyabreakstatementbecauseotherwisethe code following thebreak statement is run.At the endof theswitch statement, you candefinetheoptionaldefaultkeywordthatspecifiesthecodetorunifnoneoftheothercasestatementsevaluatetotrue(thevalueisnotonethatyouexpected).switch(expression){case"value1"://__iftrue//_______thendosomethingbreak;case"value2"://__iftrue//_______thendosomethingbreak;default://____ifallothercasesarefalse//_____________thendothis}
The following code is an example of a switch statement that checks the value of anoscillatortypeandsetsitsfrequencyvaluebasedonitsbeingasine,sawtooth,orsquarewave.Ifthe oscillator is not one of these types, the default branch executes and setsosc.frequency.valueto200.//____________________________BEGINSetupvaraudioContext=newAudioContext();varosc=audioContext.createOscillator();osc.type="sawtooth";osc.connect(audioContext.destination);//____________________________ENDSetup//____________________________BEGINSwitchstatementswitch(osc.type){
case"sawtooth":osc.frequency.value=50;osc.start(audioContext.currentTime)break;case"sine":osc.frequency.value=100;osc.start(audioContext.currentTime);break;case"square":osc.frequency.value=150;osc.start(audioContext.currentTime);break;default:osc.frequency.value=200;osc.start(audioContext.currentTime);
}//____________________________ENDSwitchstatement
TernaryOperatorIfyouarewritingaconditionalstatementthatcontainsasinglecomparisonclause(itreturnsonlyoneoftwoconditions),thenyoucanuseaternaryoperator.Theternaryoperatorhasthreeparts:anexpressionandtwoexecutedstatements.Thefirstpartisanexpressionthatistestedfortrueorfalseandisseparatedfromtheexecutedcodebyaquestionmark.Iftheexpressionevaluatestotrue,thecodetotheleftofthecolonisrun.Iftheexpressionevaluatestofalse,thecodetotherightofthecolonisrun.Thesyntaxoftheternaryoperatorlookslikethis:/*expression?iftruerunthiscode:iffalserunthiscode*/
The followingcode isanexampleof the ternaryoperator inaction.Thiscodechecks if theoscillatortypeissetto“sawtooth”.Ifitis,thefrequencyissetto50;otherwise,itissetto500.//______________________________BEGINsetupvaraudioContext=newAudioContext();varosc=audioContext.createOscillator();osc.type="sine";osc.connect(audioContext.destination);osc.start(audioContext.currentTime);//_______________________________ENDsetup//_______________________________BEGINTernaryexampleosc.type==="sawtooth"?osc.frequency.value=50:osc.frequency.value=500;//_______________________________ENDTernaryexample
Loops
Computers areverygoodat doing lotsof simple tasksvery fast.Oneof the tools available toleverage this capability is loops. Loops allow you to repeat a task until a condition or set ofconditionsaremet.Wewillcoverthefollowingtypesofloops:
■for
■while
forLoopsThefollowingcodeisanexampleofaforloopthatcountsto16andoutputseachloopnumbertotheconsole.Thetextthatfollowsexplainsthekeywordsandwhateachcomponentoftheforloopdoes.for(vari=0;i<=16;i+=1){console.log(i);}
Afor loop consists of thefor keyword and opening and closing parentheses. Inside theparenthesesarethreepartsseparatedbysemicolons.Thefirstpart istheinitializationvariable,andinthiscaseitissettozero.for(vari=0;i<=16;i+=1){console.log(i);}
The next part is the conditional statement,which is used to determine a condition to checkuponeachiterationoftheloop.Aslongasthisconditionistrue,theloopwilliterate(runanothertime).Inthefollowingexample,theconditiontellstheforlooptocontinueiteratingaslongasthevalueofthevariableiislessthanorequalto16.for(vari=0;i<=16;i+=1){console.log(i);}
Thenextpartisusedtoincrementtheinitializationvariable.Oneachloop,thevariablei isincrementedbyoneandeventuallyreaches17andstopslooping.for(vari=0;i<=16;i+=1){console.log(i);}
Thelastpartofaforloopisthecodeblockthatisdefinedbytheopeningandclosingcurlybraces. Any code that is written in between these curly braces gets repeated for each loopiteration.for(vari=0;i<=16;i+=1){console.log(i);//codeheregetsrepeatedforeachloop}
Whenfor loopsarerun,theyareveryfast.Belowisascriptthatusesanadditionalhelperfunction to pause each iteration of afor loop. The loopmodifies the frequency of a playingoscillatoroneachiteration.Thehelperfunctionpausestheloop(whichisitsonlyfunction),soyoucanheareachchange./*__________________________________BEGINHelperfunction.Ignorethiscodeitissimplybeingusedtopausetheforloop*/functionsleep(milliseconds){varstart=newDate().getTime();for(vari=0;i<1e7;i++){if((newDate().getTime()–start)>milliseconds){break;}}}//__________________________________ENDHelperfunction//__________________________________BEGINWebAudioAPIsetupvaraudioContext=newAudioContext();varosc=audioContext.createOscillator();osc.type="sawtooth";osc.frequency.value=30;osc.connect(audioContext.destination);osc.start(audioContext.currentTime);
//__________________________________ENDWebAudioAPIsetup//_________________________________BEGINaudiblefor-loopexamplefor(vari=0;i<10;i+=1){osc.frequency.value+=100;sleep(500);}//_________________________________ENDaudiblefor-loopexample
UsingforLoopswithArraysIt is common to use loops tomodify and extract data from arrays. The following code has anemptyarrayandaforloop.Thefor loopiteratesfourtimes,andoneachiteration,thestring“synth”isconcatenatedwiththeivariableandispushedtothesynthsarray.Theresultisthecreationoffourentriesinthesynthsarray,eachconsistingoftheword“synth”followedbyadashandanumber.varsynths=[];for(vari=1;i<=4;i+=1){synths.push("synth-"+i);}console.log(synths);//['synth-1','synth-2','synth-3','synth-4']
Ifyouwant tomodifyeachvalue inanexistingarray,youcandosoby looping through thearray and modifying the value at each iteration. To do this, set the conditional statementtermination value to the length of the array. In the following code, this is done withsynths.length.Youcan thenaccess the individualvaluesof thearraywithin the loopbyplacingtheiteratorvariableinsidethebracketsnexttoit.
varsynths=['synth-1','synth-2','synth-3','synth-4'];console.log(synths.length);//__Thisis4for(vari=0;i<synths.length;i+=1){console.log(synths[i]);}
Thefollowingcodeshowsamodificationofthepreviouscodewhereeachvalueinthearrayhas“0hz”appendedtoit.
varsynths=['synth-1','synth-2','synth-3','synth-4'];for(vari=0;i<synths.length;i+=1){synths[i]+="0hz";}console.log(synths);/*['synth-10hz','synth-20hz','synth-30hz','synth-40hz']*/
whileLoopsThe while loop is useful when you are unsure of how many iterations will be needed tocomplete a task.A simple example is a livepodcastwebsite that allowsusers to connect andlistenwhileashowisontheair.Asaprogrammeryoumightnotknowhowlongtheshowwilllastbutyouwanttocontinuouslycheckfornewuserconnectionsforthedurationoftheshowandallowthemtolistenin.Thepseudocodeforthisexamplemightlooksomethinglikethis:varonAir=true;while(onAir){//checkfornewvisitorsandconnectthem}
The while loop consist of the while keyword, opening and closing parentheses, andopening and closing curly braces.A conditional statement is placed in the parentheses,whichallows the loop to iterate as long as the condition remains true.When the condition becomesfalse,theloopstops.Thefollowingexampleloopsaslongasthefreqvariableisgreaterthanzero.Ateachiteration,thefreqvariabledecrementsuntilitiszeroandtheloopterminates.varfreq=7000;while(freq>0){console.log(freq);freq-=100;}
WhentoUseforLoopsandWhentoUsewhileLoopsThe rule of thumb for deciding whether to use afor orwhile loop is that afor loop istypicallyusedwhenyouknowthenumberofiterationsthatareneededtocompletetheloop,andawhileloopisusedwhenyoudon’tknowhowmanyiterationsareneededtocompletetheloop.
SummaryIn thischapter,youhave learnedhow to incorporatedecision-making intoyourprogramsusingconditionalstatements.Youhavealsolearnedhowtouseloopstoaccomplishtasksquicklyandhowloopscanbeleveragedwhenworkingwitharrays.Inthenextchapter,youwilllearnhowtoincorporatefunctionsintoyourprograms.
5Functions
Inthischapter,youwilllearnaboutfunctions,variouswaystoworkwithfunctions,andvariablescope.Functionsallowyoutowritecodeinawaythatavoidsrepetition.Theyalsoallowyoutoencapsulateyourcodeandperformaspecifictaskbasedonasetofinputs.Scopepertainstothecontext inwhichvariablesaredeclared. JavaScripthandlesvariablesdifferentlydependingontheirscope,andyouwilllearnhowtousevariablesinfunctionswhenwritingprograms.
Functions—ASimpleExampleTo explain functions, let’s look at the design of an audio effects module. Imagine a simplehardware audio effects box equippedwith a single input channel and a single output channel.Nowimaginethiseffectsboxchangestheoriginalinputinsomewaydependingonacollectionofuser-definedsettings.Inthisdesign,theoutputoftheeffectsboxistheresultoftheinputsignalcombinedwiththeusersettingsthatproducesomechangeintheoriginalsignal.
The following example shows how you might code the effects box example for a fixedselection.TheeffectsBoxfunctiontakesaninput,multipliesthatinputbytwo,andreturnstheresult.functioneffectsBox(input){returninput*2;}console.log(effectsBox(120));//Output240
Thefollowingexampleshowshowyoucanmultiplytheinputbyavalueselectedbytheuser,whichiscodedintheformofaparametercalledmultiplier.functioneffectsBox(input,multiplier){returninput*multiplier;}console.log(effectsBox(120,2));//Output240
PartsofaFunctionTocreateafunction,youstartbytypingthefunctionkeywordfollowedbyafunctionname.Immediately following the function name, you place opening and closing parentheses, and thenimmediatelyaftertheseyouplaceopeningandclosingcurlybraces.functionadd(){//functionbody}
Youcangive thefunctionplaceholdersfor inputvaluescalledparameters,whichyouplaceinsidetheparenthesesandseparatebycommas.functionadd(a,b){
}
The final part of a function is an optional return statement that outputs a value when thefunctioncompletes.functionadd(a,b){returna+b;}
Torunthefunction(alsocalledinvokingthefunction),youtypethefunctionnamefollowedbyanopeningparenthesis. If the functionhasparameters,youentervalues for these,which in thecontext of invoking the function are called arguments. You end the function with a closingparenthesis.add(2,5);//7
Ifyouinvokethefunctionwithargumentsnotdefinedbythefunction,noerrorisreturnedandthesystemignorestheadditionalarguments.add(2,5,999);//__Thethirdargumentisignoredandoutputis7
FunctionExpressionsAs an alternative to using function declaration syntax, you can write your functions usingexpressionsyntax,whereyouassignthefunctiontoavariablelikethis:varadd=function(a,b){returna+b;};add(2,3);//5
The functionexpressionsyntaxemphasizesan importantaspectof JavaScript functions: theycanbetreatedlikedataandpassedaroundbetweenvariables.Here’sanexampleofthepreviouscodewithavariablenamedcontainerthatstorestheresultofrunningtheaddfunctionwitharguments2and3:varadd=function(a,b){returna+b;};varcontainer=add(2,3);console.log(container);//5
AbstractingOscillatorPlaybackThe following function playOsc plays an oscillator and has two arguments. The first,oscType,determinestheoscillatorwaveformtype,whichfortheWebAudioAPIsupportssine,sawtooth,triangle,andsquareintheformofastring.Thesecondargumentisthefrequencyvalueinhertz.Becausethecodenecessarytogeneratetheoscillatorisencapsulatedinafunction,youcannowinvokethefunctionbywritingonlyonelineofcodeeachtimeyoucreateanoscillator.Thismeansyouavoidtherepetitionofwritingoutalloftheoscillatorcreationcodeeverytimeyoucreatetheoscillator.varaudioContext=newAudioContext();//___Initializeswebaudioapi
functionplayOsc(oscType,freq){varosc=audioContext.createOscillator();osc.type=oscType;osc.frequency.value=freq;//____freqisaparameterosc.connect(audioContext.destination);osc.start(audioContext.currentTime);}
playOsc("sine",330);//____Playsoscillatorat330hz
/*____Wecanplaymultipleoscillatorsatonceusingonlyonelineofcodeeachtime!Addinganothersineat340willcreateapulsatingeffect*/
playOsc("sine",340);
AWorkingEffectsBoxExample
Thefollowingcodedemonstratesasimplifiedworkingexampleofhowaneffectsboxmightlookwhen written as a function. The example consists of three functions. The first two functionsgenerate oscillators.The third function is the actualeffectsBox() function that accepts anoscillatorandafiltervalueasinputs,andthenappliesthefiltertotheoscillator.varaudioContext=newAudioContext();//___________________________________BEGINCustomsoundfunctioncustomSound(filterVal){varosc_1=audioContext.createOscillator();varosc_2=audioContext.createOscillator();varfilter=audioContext.createBiquadFilter();filter.type="lowpass";osc_1.type="sawtooth";osc_1.frequency.value=300;osc_2.type="sawtooth";osc_2.frequency.value=402;filter.frequency.value=filterVal||filter.frequency.value;osc_1.connect(filter);osc_2.connect(filter);filter.connect(audioContext.destination);osc_1.start(audioContext.currentTime);osc_2.start(audioContext.currentTime);}//___________________________________ENDCustomsound
//___________________________________BEGINsquarewavefunctionsquare(filterVal){varosc=audioContext.createOscillator();varfilter=audioContext.createBiquadFilter();filter.type="lowpass";osc.type="square";osc.frequency.value=100;filter.frequency.value=filterVal||filter.frequency.value;osc.connect(filter);filter.connect(audioContext.destination);osc.start(audioContext.currentTime);}//___________________________________ENDsquarewave
//___________________________________BEGINeffectsBox
functioneffectsBox(sourceInput,filterParam){sourceInput(filterParam);}//___________________________________ENDeffectsBoxeffectsBox(customSound,80);//Example
TheArgumentsObjectJavaScriptcontainsanarray-likeobjectthatallowsyoutoaccesstheargumentsofafunctionintheformofazero-basedlist.Thispseudo-arraydoesnothaveaccesstoanyofthemethodsofaconventionalarrayexceptthelengthproperty.Thefollowingcodeoutputstheargumentvaluesbyspecifyingtheargumentsobjectinconsole.log().
functionplayOsc(oscType,freq){console.log(arguments[0]);console.log(arguments[1]);}playOsc("sine",200);//sine200
You can use the arguments object to create default values for function arguments. Thefollowingcodecheckstoseeifanargumentisundefined,andifitis,setsitsvalueto“sawtooth.”functionplayOsc(oscType){//_______SetdefaultofoscTypetosawtoothif(arguments[0]===undefined){oscType="sawtooth";}returnoscType;}console.log(playOsc());//___sawtoothconsole.log(playOsc("sine"));//___sine
Theargumentsobjectcanbecombinedwiththelengthpropertyandaconditionalstatementto ensure that an error is given if any arguments are left empty. To create your own errorstatement,youusethethrowkeyword.Inthefollowingcode,theconditionalstatementcheckstoseeifthenumberofargumentsisnottwo,andiftheconditionalevaluatestotrue,thenanerrorisgiven(orthrown)toindicatethisresult.functionplayOsc(oscType,freq){if(arguments.length!==2){throw“Error!Thisfunctiontakestwoarguments"}}playOsc("sine");//___Error!Thisfunctiontakestwoarguments
Youcanaddanotherchecktoensurethatthecorrectdatatypesarebeingenteredlikethis:functionplayOsc(oscType,freq){if(arguments.length!==2){throw"Error!Thisfunctiontakestwoarguments";}//_____Checkforcorrectargumentdatatypesif(typeofoscType!=="string"||typeoffreq!=="number"){throw"Pleaseenterthecorrectargumenttypes";}}playOsc(100,true);//___Pleaseenterthecorrectargumenttypes
Youcanalsousetheargumentsobjecttolimitanargumenttoalistofspecificvalues.Thefollowingfunctiontakesasingleargumentthatisintendedtobeoneofthefourwaveformtypes.Iftheargumentisnotoneofthesefourvalues,anerroristhrown.Whenthefunctionisinvoked,thecodeloopsthroughanarrayofthefourwaveformtypes.Ifanyofthewaveformtypesmatchestheargumentvalue,avariablenamedwaveformValidissettotheBooleanvaluetrue.Thenaconditional statementchecks thevalueofwaveformValid. If it is false, an error is thrown;otherwise,thefunctionrunstocompletion.functionplayOsc(oscType){varwaveforms=["sawtooth","sine","triange","square"];
varwaveformValid=false;for(vari=0;i<waveforms.length;i+=1){if(arguments[0]===waveforms[i]){waveformValid=true;}}if(waveformValid===false){throw"pleaseentersawtooth,sine,triangleorsquareasanargument"}}playOsc("fatbeats");/*___Error:Uncaughtpleaseentersawtooth,sine,triangleorsquareasanargument___*/playOsc("square");//___works
FunctionScopeScopeisaconceptthatdefineshowonepartofaprogramcanaccessvariablesinanotherpartofaprogram. In theECMAScript5versionof JavaScript, there areonly two formsof scopes: aglobal scope and function scope (also called a local scope).Thismeans that if you declare avariablewithin a function, it is specific to that function and does not conflict with any othervariablesthathavethesamenameandaredefinedoutsideofthatfunction.Functionshaveaccessto their own variables and they also have access to any variables in a higher scope, whichincludestheglobalscope.
In one of our previous examples, we created a function to play an oscillator. Notice thatalthoughtheaudioContextvariableisnotincludedinsidetheplayOsc function, it isstillaccessible.ThisisbecauseaudioContextisdefinedinahigherscope:theglobalscope.//____audioContextisglobalvaraudioContext=newAudioContext();//____playOschasaccesstoitfunctionplayOsc(oscType,freq){varosc=audioContext.createOscillator();osc.type=oscType;osc.frequency.value=freq;//____freqisaparameterosc.connect(audioContext.destination);osc.start(audioContext.currentTime);}playOsc("sine",330);//____Playsoscillatorat330hz
Youcanusefunctionscopetoprotectvariablesdefinedinafunctionnotonlyfromvariablesdefinedinahigherscopebutfromvariablesdefinedinotherfunctions.Inthefollowingexample,therearetwofunctions.Onehasdata,andtheotherwantsdata.Thedataofthefirstfunctionisnotaccessibletotheotherfunctionbecauseitishiddeninalocalscope.functioniHaveData(){vardata="Thedata";}functioniWantData(){returndata;
}iWantData();//dataisnotdefined
Ifyoudeclare twovariableswith thesamenameandone isgloballyscoped(or inahigherscope)andtheotherislocallyscopedwithinafunction,thelocallyscopedvariableisreferencedwhenthecodeinthefunctionisrunning.
So,forexample,inthefollowingcode,themultFreqfunctiontakesasingleargumentandmultiplies it by a value that is assigned to the multiplier variable. The globally scopedmultiplier is not referenced when multFreq() is running because the function has alocallyscopedvariablewiththesamename.varmultiplier=4;/*______ThisvariableisnotreferencedbymultFreq*/functionmultFreq(frequency){varmultiplier=2;//_____Becausethisonehasthesamenamereturnfrequency*multiplier;}console.log(multFreq(200));//400console.log(multiplier);//4
If the locally scoped multiplier variable declaration inside the previous function isremoved, thenduring functionexecution, the codewill lookoutside the function for avariablewiththereferencedname.varmultiplier=4;functionmultFreq(frequency){/*__Thereisnolocalmultipliervariablesoitfindsoneinthescopeaboveit__*/returnfrequency*multiplier;}console.log(multFreq(200));//800
WhyYouShouldAlwaysDeclareYourVariableswithvar
InJavaScript,theuseofglobalvariablesshouldgenerallybekepttoaminimum.Thisisbecausewhenprogramsgetlarge,theaccumulationofglobalvariablesincreasesthelikelihoodofnamingcollisions. Typically, this is not a problem for small applications. However, when programsbegintogrow,theywillusuallyincorporatelibrariesandthird-partyscriptsthatdependonsomeglobalvariables.Accidentlyoverwritingtheseglobalvariablescancauseyourprogramtobreak.
Whenyoudeclareavariablefromwithinafunctionwithoutthevarstatement,thevariableisreferencedfromtheglobalscopewhenthefunctionisinvoked.Thiscanhavethesideeffectofoverwritingapreexistingglobalvariablewith thesamenameandcreatinganunexpectednamecollision.Thefollowingcodedemonstrateshowthiscanhappen.
The following example contains a global variable called multiplier. There is also avariablecalledmultiplier insideof themultFreq function that isnotdeclaredusing thevarstatement.Whenthefunctionis invoked,themultipliervariablereferencestheglobal
multipliervariable,changingitsvaluefrom4to2!This isanexampleofwhyyoushouldalwaysdeclareyourvariableswiththevarstatement.varmultiplier=4;functionmultFreq(frequency){multiplier=2;//____________Noticenovardeclaration!returnfrequency*multiplier;}console.log(multFreq(200));//400console.log(multiplier);//____Changedto2!
VariableHoistingWhether you declare your variables globally or within a function, you should always declarethem at the top of the current scope. The reason for this is a phenomenon called hoisting. Tounderstandhoisting,youmustfirstunderstandthatvariabledeclarationandinitializationaretwodifferentthings.Inthefollowingcode,avariableisdeclaredusingthevarstatementandthenitisinitializedonthenextline.varmyData;//_______________________________variabledeclaredmyData="importantdatagoeshere";//__variableinitialized//ThefollowingvariableisdeclaredandinitializedinonelinevarplayOsc=false;
When you declare a variable, the JavaScript interpreter immediately (behind the scenes)decouplesthedeclarationfromtheinitializationandmovesthevariabledeclarationtothetopofthecurrentscope.Thefollowingexampledemonstratesthis.Thecodeontheleftshowsafunctionnamedrunthatcontainsavariablenamedtest,whichisdeclaredafteritisinitialized.Whenthisfunctionisinvoked,JavaScriptchangestheorderandplacesthedeclarationatthetopofthecurrentscope,ineffectmakingthefunctionlookidenticaltothecodeontheright.Thisiswhythegloballyscopedtestvariable isnotoverwrittenwhen thefunction is invoked,even though itappears at first glance that it should be, because the localtest variable is not yet declared.Because of hoisting, it is considered best practice to declare your variables at the top of thecurrentscope,whichiswheretheywillbedeclaredanyway.
HowHoistingAffectsFunctionsInadditiontohoistingaffectingvariables,italsoaffectsfunctions.Andhoistingworksdifferentlybasedonwhetherthefunctioniswrittenwithdeclarationorexpressionsyntax.
Considerthefollowingfunctiondeclaration.Inthiscode,thefunctionisinvokedbeforeit isdeclared,yetitstillworks!Thisisbecausebehindthescenes,thedeclarationishoistedtothetopofthescope,whichallowsyoutoexecutethefunctioneventhoughitisnotyetdeclared.multFreq(200,2);/*__Thisstillworkseventhoughitisinvokedbeforeitisdeclared!_*/functionmultFreq(input,val){returninput*val;}
Thefollowingexampleofthesamefunctionwrittenusingexpressionsyntax,however,throwsan error. This happens because function expressions are treated like variables, with thedeclarationbeinghoistedtothetop.Rememberthattheinitializationofthevariablestillhappenswhere the variable is initialized in the code. In this case, the function is run before theinitialization that defines the function occurs. The lesson here is that when you use functionexpressions,youmustdeclare functionsbeforeyou invoke them.This isgoodpracticewithallyourfunctionsasitmakesyourcodelessconfusingandmorereadable.multFreq(200,2);//___error!“multFreqisnotafunction”.varmultFreq=function(input,val){returninput*val}
AnonymousFunctionsAnonymous functions are functions that do not have a name. Technically, the function in thefollowingcodeisananonymousfunctionbecausethevariableitisassignedtoisnotthefunctionname.Itisthecontainernameforananonymousfunction.varmultFreq=function(input,val){returninput*val;}
Togivethisfunctionaname,youdoitlikethis:varmultFreq=functionnameGoesHere(input,val){returninput*val;}
Note,however,thattoinvokethefunction,youusethevariablenamethatitisassignedto.multFreq(100,2);//200
InJavaScript,itispossibletocreateafunctionthatisinvokedimmediatelyafteritisdeclared.ThistypeoffunctioniscalledanimmediatelyinvokedfunctionexpressionorIIFE(pronounced
“iffy”).Thismethod is useful if youwant to briefly encapsulate and run a block of code onlyonce.Thesyntaxlookslikethis://______________________BEGINIIFE(functionrun(){return"data";}());//______________________ENDIIFE
Toviewtheoutput,youcanwrapitinconsole.log().console.log(//______________________BEGINIIFE(functionrun(){return"data";}());//______________________ENDIIFE);
Thefirstthingtonoticeisthatthefunctioniswrappedinparentheses.Thisisoptional,butisconsideredbestpracticebecause ithelpsdifferentiate theconstructsyntactically fromnon-IIFEfunctions.(functionrun(){return"data";}());
Thenext thing tonotice is theparentheses toward theendof thefunctionbefore theclosing,encapsulatingparenthesis.Thissyntaxiswhatinvokesthefunction.(functionrun(){return"data";}());
To add parameters and arguments, you put parameters in the first set of parentheses andarugmentsinthesecondsetofparentheses.(functionadd(a,b){//_____parametersreturna+b;}(2,3));//___________________arguments
ClosuresOne of themost difficult aspects of the JavaScript language for new programmers to grasp isclosures.Understanding closureswill ultimately allowyou towrite cleaner codewhile givingyouapowerfultooltosolveahostofproblemsyouwill inevitablyruninto.Understandingtheconceptofclosurecanbeabitdifficultatfirst.But inthelongterm,thebenefitsareworththetimeinvestment.
WhatIsaClosure?
Aclosureisaninnerfunctionthathasaccesstothescopeofitsouterenvironmentevenafterthatouter environment has returned. To understand what this means, you must first solidify yourunderstanding of scope. The following example demonstrates how a function has access to itslocalscope,theglobalscope,anditslocalarguments.varglobalVariable="globalvariable";functiondoSomething(argInput){varlocalVariable="localvariable";console.log(argInput);console.log(globalVariable);console.log(localVariable);}
doSomething("argumentinput");/*_________Thisoutputs:"argumentinput""globalvariable""localvariable"becausethefunctionhasaccesstoitsownscopeandtheouterscope.*/
Ifafunctionisdefinedinsideanotherfunction, it toohasaccess to thedataof theharboringfunction,aswellasitsownlocallyscopedvariables.Inthefollowingexample,testScope()isaharboringfunctionforinnerFunction().varglobalVariable="globalvariable";
functiontestScope(argInput){vartestScopeLocalVariable="localvariablefromtestScope";//____TheinnerfunctionhasaccesstoeverythingoutsideofitfunctioninnerFunction(){varlocalVariable="localvariablefrominnerFunction";console.log(argInput);console.log(globalVariable);console.log(testScopeLocalVariable);console.log(localVariable);}innerFunction();}
testScope("argumentinput");
/*Theconsolelogs:"argumentinput""globalvariable""localvariablefromtestScope""localvariablefrominnerFunction"*/
As we mentioned, a closure is an inner function that has access to the scope of its outerenvironment even after that outer environment has returned. The previous examplesdemonstratedscopeaccess.Thefollowingexampledemonstrateswhatitmeansforafunctiontohavescopeaccessevenafter theouterenvironmenthasreturned.Theouterenvironmentcanbeeithertheglobalenvironmentoranotherfunction.ThefollowingcodeincludestheeffectsBoxfunctionthatcontainsasinglevariablenamedcomponent.TheeffectsBoxfunctionreturnsa function that returns the value ofcomponent.When the initialeffectsBox function isinvoked,itreturnsafunctiondeclarationnamedopenEffectsBox totheouterscope(inthiscase the global scope). This openEffectsBox function declaration is then assigned to a
variable called getComponent, which is then invoked and returns the string “Pulled outcomponent.”
Theimportantthingtorealizehereisthataclosure(theinnerfunction)canreturndata(suchasthecomponentvariable)fromitscontainingenvironment[inthiscaseeffectsBox()]evenafterthatouterenvironment[effectsBox()]hasreturned.functioneffectsBox(){varcomponent="Pulledoutcomponent";returnfunctionopenEffectsBox(){returncomponent;};}vargetComponent=effectsBox();/*___stores"openEffectsBox"functioninavariable.*/console.log(getComponent());//"Pulledoutcomponent"
The previous example can be modified to demonstrate how state can be modified andretainedusingtheclosure.Inthiscode,thereisanadditionalcountervariablethatincrementseachtimetheinneropenEffectsBoxfunctionisinvoked.Sinceclosuresallowaccesstothescope of a containing function even after that containing function has returned, the returnedfunctioncancontinuetoincrementthecountervariableandhaveaccesstoitsstate.functioneffectsBox(){varcounter=0;varcomponent="Pulledoutcomponent";returnfunctionopenEffectsBox(){returncomponent+""+(counter+=1);};}vargetComponent=effectsBox();//___stores"openEffectsBox"functioninavariable.
getComponent();//"Pulledoutcomponent1"getComponent();//"Pulledoutcomponent2"getComponent();//"Pulledoutcomponent3"getComponent();//"Pulledoutcomponent4"
Hereisanexampleofafunctiondesignedtoplayanoscillatorbyreturninganinnerfunctionthat remembers the outer environment’s state. This example shows how the inner functionaccesses the function arguments of the outer function even after the outer function returns. TheplayOscfunctiontakesparametertype,whereastheinnerfunctionitreturnstakesparameterfreq.Theouterfunctionisinvokedwiththeargument“sine”;thereafter,theinnerfunctionisinvokedwithafrequencyvalue.Theresultisasinewavethatplaysatasetfrequencyvalueof140Hz.varaudioContext=newAudioContext();
functionplayOsc(type){
returnfunction(freq){varosc=audioContext.createOscillator();osc.type=type;osc.frequency.value=freq;osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);};}varsinewave=playOsc("sine");sinewave(140);//____________Playssinewaveat140hz
Closure isanadvancedconcept thatcanbeused toprotectaportionofaprogramfrom theglobalscope,retainstate,andorganizeyourcode.Itsspecificusecaseswillgraduallybecomemoreapparentasyourskillasaprogrammerdevelops.Fornow,itisimportanttograspwhataclosureis.
CallbackFunctionsA callback is a function that is used as an argument to another function. The followingexampledemonstratesadditionoftwonumbersusingacallback.functiondoMath(callback){returncallback();}
functionaddTwoNumbers(){return2+2;}doMath(addTwoNumbers);//4
When working with callbacks, you will often see function invocations where the callbackdeclarationisplaceddirectlyinafunctionargument.functiondoStuff(callback){returncallback();}
doStuff(function(){//___Callbackdeclarationisuseddirectlyreturn//___data});
Thefollowingfunctionisanexampleofusingacallbacktomakeafunctionmoreflexible.ThecalculateFrequencies function is designed to take three arguments. The first two arenumbersandthethirdisacallbackthatmanipulatestheotherarguments.Iftheuserdoesnotuseacallback,thenthefunctiondefaultstomultiplyingthetwoargumentstogether.functioncalculateFrequencies(a,b,callback){if(callback===undefined){returna*b;}else{returncallback(a,b);}}functiondiff(a,b){returnMath.abs(a-b);}
console.log(calculateFrequencies(200,2));//400___Multipliesnumbers
console.log(calculateFrequencies(1000,4000,diff));/*3000___usescustomcallbacktofindthedifference*/
Thepreviousexampledemonstrateshowpassingacallbacktoafunctionprovidestheactiontakenbythecallback,whereaspassingnonfunctionvaluesprovidesdatainput.
WorkingwithJavaScript’sBuilt-InCallbackFunctionsLearningtodesignyourownfunctionsthatusecallbacksisanadvancedtopic.Asabeginner,themoreimportantthingforyoutoknowishowtousepreexistingmethodsthathavebeendesignedto use callbacks. The following are two examples of built-in JavaScript methods that usecallbackstohelpyouworkwitharrays.
ArrayMethod Description
filter() Compareseachelementinanarraytoaconditionalstatementandreturnsanewarrayofelementsthatmeetthecondition
map() Callsafunctiononeachelementinanarrayandreturnsanewarraywiththemappedvalueofeachelementintheinputarray
filter()Thefiltermethodcompareseachelementinanarraytoaconditionalstatementandreturnsanew array of only those elements that meet the filter condition. The following example usesfilter()toloopthroughanarrayoffrequencyvaluestocreateanewarrayofvaluesgreaterthanorequalto1000.varfreq1=1200,freq2=570,freq3=100,freq4=1500;
varfrequencyList=[freq1,freq2,freq3,freq4];
varfilteredFrequencies=frequencyList.filter(function(value){returnvalue>=1000;});
console.log(filteredFrequencies);//___[1200,1300]
map()Themap function calls a function on each element in an array and returns a new array thatcontainsthemappeddataforeachelementintheinputarray.
Thefollowingexampleusesmap() to add100 toeachvalue inanarrayand returnanewarraynamednewFreqs.varfreqs=[100,200,300];varnewFreqs=freqs.map(function(val){returnval+100;});
console.log(newFreqs);//__[200,300,400]
Thecallbackfunctionsofbothmap()andfilter()takethreearguments.Inorderoftheirposition,thesearevalue,index,andarray.Thevalueargumentisthearrayvalueatthecurrent index, theindex argument is the current indexvalue, and thearray argument is thearraythatthecallbackisbeingappliedto.Inthefollowingexample,amapmethodisappliedtoanarrayandallthreeargumentsareloggedtotheconsole.varfreqs=[100,200,300];varnewFreqs=freqs.map(function(val,index,arr){varmessage="currentvalue:"+val+"currentindexindex:"+index+"array:"+arr;console.log(message);returnval;});/*___Thislogsthefollowingtotheconsolecurrentvalue:100currentindex:0array:100,200,300currentvalue:200currentindex:1array:100,200,300currentvalue:300currentindex:2array:100,200,300*/
RecursionRecursionisanadvancedprogrammingtopic,anditwillonlybeexploredbrieflyinthischapter.
Arecursivefunctionisafunctionthatcallsitself.Thefollowingisanexampleofarecursivefunction.functionx(){returnx()}
Ifyou run thepreviouscode, itwill crashyourbrowser.This isbecause,whena recursivefunctionrunsindefinitely,iteventuallyusesuptheresourcesofyourcodeinterpreter(inthiscasethewebbrowser)andcreatesanerror.Touserecursioneffectively,youneedtosetaconditiontoterminatetherecursion.Thisconditioniscalledthebasecase.
ThefollowingexampleisarecursivefunctionnamedloopFromTothatcontainsaworkingbase case.loopFromTo takes two arguments, and both are numbers. In the function body, aconditionalisusedtocheckiftheargumentnamedstartislessthantheargumentnamedend.Aslongasthisconditionistrue,loopFromTocallsitselfandoneachiterationincrementsthestartargumentbyone.Thiscontinuesuntiltherecursionterminateswhenstartceasestobelessthanendandtheconditionalstatementevaluatestofalse.
functionloopFromTo(start,end){console.log(start);if(start<end){returnloopFromTo(start+=1,end)}}
loopFromTo(1,8)//______1,2,3,4,5,6,7,8
Recursivefunctionscanbeusedinplaceof loopingconstructsandareaninvaluabletool inmanycomplexalgorithms.If recursionseemsconfusingdon’tworry,youcanprogramperfectlygoodapplicationswhileyoubecomefamiliarwithit.
SummaryInthischapter,youlearnedhowtocreateandusefunctions.Inthenextchapter,youwillexpandyourunderstandingofJavaScripttoincludeaconceptcalledobject-orientatedprogramming.
6Objects
So far, we discussed five of JavaScript’s six data types. These are string, number, Boolean,undefined,andnull.Thesearecalledprimitivedatatypes.Anythingthatisnotaprimitivedatatypeisoftheobjectdatatype.Inthepreviouschapter,youlearnedaboutfunctions,whichareoftheobjectdata type.Inthischapter,youwill learnhowtoprogramusingobject literals,whicharealsooftheobjectdatatype.
JavaScriptDataTypesTheJavaScriptdatatypesare:
■String
■Number
■Boolean
■Undefined
■Null
■Object
The object data type includes functions, arrays, and object literals.Arrays and functions havealready been explored, so here is a general definition of object literals: Object literals are acollectionofcomma-separatedkey-valuepairsthatarecontainedwithincurlybraces.
Note: Developers in the JavaScript world commonly refer to object literals asobjects.However,objectliteralsandtheobjectdatatypearetwodifferentthings.Onewaytounderstandthedifferenceistorecognizethattheobjectdatatypeisacategorythatcontainsobjectliterals,functions,andarrays.
Inthefollowingcode,anobjectnamedobjiscreatedandthevalueswithincurlybracesareassignedtoit.varobj={key1:"value1",key2:"value2"};
Akeyissimilartoavariable,andavalueissimilartothedataassignedtoavariable.Thekeyandvalueofanobject iscalledaproperty fornonfunctionsassignedtoakey,oramethod forfunctionsassignedtoakey.varobj={key:"value",//___ThisisapropertydoSomething:function(){//___Thisisamethod}};
Conceptually, object literals are used to model real-world elements in your code. So, forexample,thefollowingobjectisusedtomodelamusicalbum.//_________________Thisisanobjectthatcontainsalbumdatavaralbum={name:"ThrillerFunk",artist:"JamesJackson",format:"wave",sampleRate:44100}
Toaccessdatafromanobject,youcanusedotnotation,whichlookslikethis:album.name;//ThrillerFunkalbum.artist;//JamesJacksonalbum.format;//wavealbum.sampleRate;//44100
Alternatively,youcanusebracketnotationtoaccessvaluesinanobject.album["sampleRate"];//44100
Ifyouuseabracketnotation,youmusttypethekeyintheformofastring.album["sampleRate"];//__Thekeyisastring
You can usemethods tomodify or retrieve data from an object. Here is an example of anobjectthatcontainsamethodthatreturnsthenameandartistinformationofanobjectnamedsong.varsong={name:"FunkyShuffle",artist:"JamesJackson",format:"wave",sampleRate:44100,//_____________________________BEGINMethodnameAndArtist:function(){return"Name:"+song.name+"|"+"Artist:"+song.artist}
//_____________________________ENDMethod}
Youcaninvokemethodswithdotnotationandtrailingparentheses.//__________________________________BEGINmethodinvocationsong.nameAndArtist();//Name:FunkyShuffle|Artist:JamesJackson//__________________________________ENDmethodinvocation
LoopingthroughObjectsToloopthroughthekeysandvaluesofanobject,youuseaforin loop.Youcodeaforinlooplikethis:varsong={name:"FunkyShuffle",artist:"JamesJackson",format:"wave",sampleRate:44100}//______________________BEGINforinloopfor(varpropinsong){console.log(prop+":");//__Outputseachkeyconsole.log(song[prop]);//__Outputseachvalue}//______________________ENDforinloop
The structure of aforin loop consists of thefor keyword followed by a variable thatrepresentsthevalueofeachproperty.Inthepreviousexample,thisvariablewasnamedprop.Thevariablenameisfollowedbytheinkeywordandthenameoftheobjectyouwanttoloopthrough.
Oftenyouwillwant tomodifythepropertiesofanobjectyouareloopingthroughwhilenotmodifyinganyofitsmethods.Onewayyoucandothisisbyusingaconditionalstatementandthetypeofoperatortoactonlyonpropertyvaluesthatarenotfunctions.Thisusageisshowninthefollowingcode:varsong={name:"FunkyShuffle",artist:"JamesJackson",format:"wave",sampleRate:44100,nameAndArtist:function(){return"Name:"+song.name+"|"+"Artist:"+song.artist;}};for(varpropinsong){if(typeofsong[prop]!=="function"){console.log(song[prop]);//___Omitsmethods}}
WhentoUseObjectsRatherThanArraysYouhaveprobablynoticedthatobjectsandarraysaresimilarbecausetheyallowyoutoorganizecollectionsofdata.Ifyouarecuriousaboutwhentouseanarrayratherthananobject,theruleofthumbisthatiftheorderofthedatamatters,youshouldalwaysuseanarray.ThereasonforthisisthatthereisnothingintheJavaScriptspecificationthatguaranteestheorderinwhichkey-valuepairsofanobjectarereturnedinaloop.
HowtoCheckIfanObjectHasAccesstoaParticularPropertyorMethod
Ifyouwanttocheckwhetherapropertyormethodisavailabletoanobject,youcanusetheinoperator.varsong={name:"FunkyShuffle",artist:"JamesJackson",getArtist:function(){returnsong.artist;}};console.log("artist"insong);//trueconsole.log("getArtist"insong);//true
CloningObjectsIfyouwanttocreateanobjectthathasaccesstoanotherobject’spropertiesandmethods,whilebeingextensible,youcanusetheObject.create()function.Thefollowingexampleshowsanobjectbeingclonedusingthismethod.vareffects={reverbs:{hall:"Hallreverbbeingused",plate:"Platereverbbeingused",smallRoom:"Smallroomreverbbeingused"},guitar:{flange:"Flangebeingused",wahWah:"Wahwahbeingused"}};varupdatedEffects=Object.create(effects);console.log(updatedEffects.reverbs);//returnsreverbobjectconsole.log(updatedEffects.guitar);//returnsguitarobject
Youcanthenextendthenewlycreatedobjectwithpropertiesandmethods.
updatedEffects.filters={lowPass:"Lowpassfilterbeingused",highPass:"Highpassfilterbeingused"};console.log(updatedEffects.filters);//returnsfilterobjectconsole.log(effects.filters);//undefined
PrototypalInheritanceItisimportanttounderstandthatObject.create()doesnotliterallycopythepropertiesandmethodstoanewobjectbutprovidesareferencetothepropertiesandmethodscontainedintheparentobject(s).Thishierarchyofreferencesbetweenobjectsiscalledprototypal inheritance.The following code shows this by cloning multiple objects and including comments of thehierarchyofpropertyaccessibility.varsynth={name:"Moog",polyphony:32};
varsynthWithFilters=Object.create(synth);//clonesynth//synthWithFiltersnowhasaccesstonameandpolyphonypropertiessynthWithFilters.filters=["lowpass","highpass","bandpass"];//addproperty/*Theoriginalsynthobjectdoesnothaveaccesstothefiltersproperty.*/varsynthWithFiltersAndEffects=Object.create(synthWithFilters);//clonesynthWithFilterssynthWithFiltersAndEffects.effects=["reverb","flange","chorus"];//addproperty/*NeitherthesynthobjectnorthesynthWithFiltersobjecthaveaccesstotheeffectsproperty*/
The"this"KeywordJavaScriptcontainsakeywordcalledthis thatisusedinmethodstorefertoanobject.Inthefollowingcode, themethodnamednameAndArtist references its containingobject directlybyusingitsname,whichissong.varsong={name:"FunkyShuffle",artist:"JamesJackson",format:"wave",sampleRate:44100,//_____________________________BEGINMethodnameAndArtist:function(){return"Name:"+song.name+"|"+"Artist:"+song.artist;}//_____________________________ENDMethod};
Thereferencetosongcanbereplacedwiththethiskeyword,andtheresultisthesame.nameAndArtist:function(){return"Name:"+this.name+"|"+"Artist:this.artist";};
ThebindFunctionTheusefulnessof thethis keywordbecomesapparentwhenyou realize thatany functionormethodcanbeappliedtoanyobject.Theeasiestwaytodemonstratethisisbyusingthebuilt-inJavaScriptmethodcalledbind.Thebindmethodpointsafunction’sthisvaluetotheobjectspecifiedinthefirstargument(theboundobject).Youcantheninvokethefunctionontheboundobject. In the following code,bind points thegetName function’sthis value to an objectnamedalbum.varsong={name:"FunkyShuffle",artist:"JamesJackson",format:"wave",sampleRate:44100};
functiongetName(){returnthis.name;}vargetNameOfSong=getName.bind(song);/*assignboundfunctiontoavariable*///_______Theninvokeit!console.log(getNameOfSong());//FunkyShuffle
Ifyouwanttospecifyargumentsinafunctioncreatedwithbind,youcandothisinoneoftwoways.The first is to specify the arguments in thenewly created function. In the followingexample, a function nameddescriptor is invoked on an object namedblastSound. AnargumentisthenpassedtothedescribeBlastSoundfunction.varblastSound={name:"Blast"};
functiondescriptor(message){returnthis.name+":"+message;}
vardescribeBlastSound=descriptor.bind(blastSound);console.log(describeBlastSound("Thisisanexplosivesound"));//Blast:Thisisanexplosivesound
Alternatively,youcanspecifytheargumentsinthestatementwhereyoubindthefunctiontotheobject.Youdothisbyfirstspecifyingtheobjecttobindto,thenspecifyingargumentsyouwanttouseandseparatingthemwithcommas,asinthefollowingexample:
vardescribeBlastSound=descriptor.bind(blastSound,"Thisisanexplosivesound");console.log(describeBlastSound());/*Blast:Thisisanexplosivesound*/
Asyoucansee,evenwhenafunctionhasnotbeenwrittenasamethodonaparticularobject,youcanstillapplythefunctiontothatobject.Thisalsomeansthatyoucanuseamethodofoneobject and apply it to a completelydifferent object.The following codeuses amethodnamedgetNameAndArtistofanobjectnamedsongandappliesittoanobjectnamedAlbum.
varalbum={name:"FunkyShuffle",artist:"JamesJackson",format:"wave",sampleRate:44100};varsong={name:"AnalogueHeaven",artist:"TheKeepItReels",getNameAndArtist:function(){
return"Name:"+this.name+"|Artist:"+this.artist;}};vargetNameOfAlbum=song.getNameAndArtist.bind(album);console.log(getNameOfAlbum());/*Name:FunkyShuffle|Artist:JamesJackson*/
Ifafunctionisinvokedoutsidethecontextofanobject,itsthisvaluepointstooneoftwovalues,dependingonwhetherstrictmodeisusedornot.Ifstrictmodeisused,itsthisvalueisundefined. If strict mode is not used, itsthis value points to an invisible object called theglobalobject,whichcontainsallthebuilt-inpropertiesandmethodsofthewebbrowser.Youcanview the value ofthis by usingconsole.log(this) in the global scopewithout strictmode.
SummaryInthischapter,youlearnedhowtoprogramwithobjects.Inthenextchapter,youwilllearnthebasicsoftheWebAudioAPInodegraphandworkingwithoscillators.
7NodeGraphsandOscillators
Inpreviouschapters,you learned thebasicsofworkingwithJavaScriptdata typesandhowtousetheWebAudioAPItogeneratebasictones.Inthischapter,youwilluseyourunderstandingofJavaScripttogetabetterunderstandingoftwocorefeaturesoftheWebAudioAPI:nodegraphsandoscillators.
TheAudioContext()MethodTheWebAudioAPIisaccessedbyusingacollectionofpropertiesandmethodsofanobjectthatyoucreateusingtheAudioContext()method.varaudioContext=newAudioContext();
AudioContext() isaconstructor that returnsanobjectwhenyouuse thekeywordnew.ConstructorsandthenewkeywordareexplainedinChapter12.Fornow,theimportantthingtounderstand is that AudioContext() returns an object containing all of the methods andpropertiesthatyouusetoaccesstheWebAudioAPI.
NodeGraphsAnodegraph is a collectionof nodes.Anode in a nodegraph is anobject that represents anaudio input source, such as an oscillator, or an object designed to manipulate an audio inputsource,suchasafilter.Thesenodesareconnectedtogetherusingamethodnamedconnect.
Thefollowingcodeisanexampleofanoscillatornodeconnectedtoafilternode."usestrict";varaudioContext=newAudioContext();//_____________BEGINcreateoscillatorandfiltervarfilter=audioContext.createBiquadFilter();varoscillator=audioContext.createOscillator();
//_____________ENDcreateoscillatorandfilter//______________BEGINconnectoscillatortofilteroscillator.connect(filter);//_____________ENDconnectoscillatortofilter//_____________BEGINconnectfiltertocomputerspeakersfilter.connect(audioContext.destination);//_____________ENDconnectfiltertocomputerspeakers
//_____________BEGINstartoscillatorplaying
oscillator.start(audioContext.currentTime);//_____________ENDstartoscillatorplaying
Inthepreviouscode,theoscillatorobjectiscreatedusingthecreateOscillatormethodoftheaudiocontextandstoredinavariablenamedoscillator.Youcreatethefilterobjectina similar way by invoking the createBiquadFilter method of audioContext. Theoscillator is connected to the filter usingconnect(). Thefilter is connected to apropertynameddestination.Thedestinationrepresentstheoutputofyourcomputer’saudio system. To start the oscillator playing, you use a method of the oscillator objectnamedstart.Thestartmethod takes one argument that determines the time the oscillatorstarts playing. The value ofaudioContext.currentTime is the current time in secondswithintheWebAudioAPI,startingwhenAudioContextwasinvoked.(ThetopicoftimeisdiscussedinChapter20.)
OscillatorsOscillators,likeallWebAudioAPInodes,havetheirowncustompropertiesandmethods.Thefollowingmethodsandpropertiesarediscussedinthischapter.
Method Description
start Startsoscillatorplaying
stop Stopsoscillatorplaying
Property Description
onended Executesacustomfunctionwhenoscillatorstops
type Setsthetypeofwaveformassignedtotheoscillator
frequency Setsthefrequencyvalueoftheoscillatorinhertz
detune Setsanoffsetofthecurrentfrequencyvalueincents
ThestopMethodThe stop method determines when an oscillator stops. It takes one numeric argument thatrepresentsatimevalueinseconds.Thefollowingcodestartsanoscillatorplayingandstopsit3secondsintothefuture.varoscillator=audioContext.createOscillator();oscillator.connect(audioContext.destination);oscillator.start(audioContext.currentTime);oscillator.stop(audioContext.currentTime+3);
TheonendedPropertyIfyouwanttolaunchafunctionaftertheoscillatorstopmethodhasrun,youassignthatfunctiontotheonendedproperty.Thefollowingcodeoutputsthestring“Oscillatorhasstopped”totheconsoleafteritsstopmethodcompletes.varoscillator=audioContext.createOscillator();oscillator.connect(audioContext.destination);oscillator.start(audioContext.currentTime);oscillator.stop(audioContext.currentTime+3);oscillator.onended=function(){console.log("Oscillatorhasstopped");};
HowtoStopOscillatorsandRestartThemWhen an oscillator is stopped, it cannot be restarted. Instead, it must be recreated and thenstarted. To demonstrate this, the following code attempts to restart an oscillator after it hasstopped,whichresultsinfailure.varaudioContext=newAudioContext();varoscillator=audioContext.createOscillator();oscillator.connect(audioContext.destination);oscillator.start(audioContext.currentTime);oscillator.stop(audioContext.currentTime+3);
oscillator.onended=function(){oscillator.start(audioContext.currentTime);//fails!};
The following code recreates anoscillator and starts it playing1 second after thepreviousoscillatorstops.varoscillator=audioContext.createOscillator();oscillator.connect(audioContext.destination);oscillator.start(audioContext.currentTime);oscillator.stop(audioContext.currentTime+3);
oscillator.onended=function(){oscillator=audioContext.createOscillator();oscillator.connect(audioContext.destination);oscillator.start(audioContext.currentTime+1);/*startinonesecond*/};
ThetypePropertyThetypepropertyofanoscillatorsetsitswaveformtypeintheformofastring.Therearefourpredefinedwaveformshapesavailable.
■sawtooth
■sine
■square
■triangle
Youassignawaveformtypetoanoscillatorlikethis:varaudioContext=newAudioContext();varoscillator=audioContext.createOscillator();oscillator.connect(audioContext.destination);oscillator.type="sawtooth";oscillator.start(audioContext.currentTime);
Thedefaultwaveformtypeissine.
ThefrequencyPropertyTo set an oscillator’s frequency, you must set the frequency property to a number. Thefrequencyvalueisrepresentedinhertz.
varoscillator=audioContext.createOscillator();oscillator.connect(audioContext.destination);oscillator.frequency.value=80;//_____80hertzoscillator.start(audioContext.currentTime);
ThedetunePropertyThedetunepropertyisexpressedincents.IntheWesternmusicscale,thereare100centsperhalf-step note. This makes it easy to create musical note relationships using detune. Thefollowingcodeplaysanoteatafrequencyof130.81hertzandisthefrequencyofaC3note.Theoscillatorstops,andahalf-secondlaterasecondnoteplayswiththesamefrequency.valueandadetune.valueof100cents,makingthenotevalueC#3.
varaudioContext=newAudioContext();varoscillator=audioContext.createOscillator();oscillator.connect(audioContext.destination);oscillator.frequency.value=130.81;//________C3
oscillator.start(audioContext.currentTime);oscillator.stop(audioContext.currentTime+2);
oscillator.onended=function(){oscillator=audioContext.createOscillator();oscillator.frequency.value=130.81;//C3noteoscillator.detune.value=100;/*setsthenotetoonehalfstephighertoC#3*/oscillator.connect(audioContext.destination);oscillator.start(audioContext.currentTime+0.5);oscillator.stop(audioContext.currentTime+2.5);};
SummaryInthischapter,youlearnedthebasicsofnodegraphsandoscillators.Inthenextchapter,youwilllearn the basics of HTML and CSS and create the interface for your first Web Audio APIapplications.
8UsingHTMLandCSStoBuildUserInterfaces
In this chapter, youwill learn thebasicsofHTMLandCSS,givingyou thenecessary tools tobuilduserinterfacesforyourWebAudioAPIapplications.Youwilldothisbybuildingauserinterfaceintendedtotriggeranoscillatorthatincludesinteractivecontrolstoselectfrequencyandwaveformtype.Inthenextchapter,youwillcombinetheinterfacewithJavaScriptcodetobuildyourfirstworkinginteractiveapplication.
WhatIsaUserInterface?Auserinterface,alsocalledaUI,isthepartofanapplicationthatauserinteractswith.Amusicsynthesizer’sUI is thekeyboard,aswellas theknobsandsliders thatallowyou tomodify thesound of the instrument. In a website or application, the UI can include buttons, form fields,sliders,scrollbars,andotherelementsthatfacilitateusercontrol.
HTMLHTMLstandsforhypertextmarkuplanguageandisthelanguageusedtocreatestaticwebsites.InChapter1, you learned thatHTMLconsists of elements, sometimes referred to as tags, thatmakeup thepageof anHTMLdocument.Tobe treatedas anHTMLdocument, a filemustbesavedwith.htmlappendedtoitsname.Afileextensionisagroupofcharactersplacedafteraperiodinafilenamethatindicatesthefile’sformat.Inthecaseofafilenamedindex.html,thefileextensionis.html.
The following code is from theHTML template you created in Chapter 1. It consists of acollectionofelementsrequiredtomakeadocumentW3Ccompliant.W3CstandsforWorldWideWeb Consortium; this group is responsible for the development of web standards. UnlikeJavaScript, HTML does not return errors if your code is written incorrectly, so you needadditional tools to findHTML errors. You can test the compliance of anHTML document by
running your code through the HTML validation tool at the following URL:https://validator.w3.org.<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>app</title><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"></head><!--_____________________________________________BEGINAPP--><body>
</body><!--_____________________________________________ENDAPP--></html>
ExplanationoftheHTMLTemplateThefirstelementinanHTMLfile,<!DOCTYPE>,declareswhatversionofHTMLthepageiswritten in. ForHTML5, use <!DOCTYPEhtml>.As of thiswriting,HTML5 is the newestversionoftheHTMLspecification.Thenextelement,<html>,encapsulatestheremainderofthecode. <html> represents the “root” of the document and contains the <head> and <body>elements.The<head>elementdescribesinformationaboutthedocument,whereasthe<body>elementdescribesthecontentonthevisiblepage.
Within the<head>element, the<meta> tagdefineswhichkeyboardcharacterencoding isusedonthewebpage.Characterencodingsrepresents thewaythatcharactersonyourphysicalkeyboard get translated to text. UTF-8 coversmost languages and is also the standard for themodernweb.The<title>elementisusedtogiveyourpageatitle.Theremainingcodeinsidethe<head>elementincludesreferencestoexternalfilesthatcontainJavaScriptandCSScode.
Immediately before the body element is a comment.HTML comments arewritten using thefollowingsyntax:<!—commentgoeshere-->
Insidethe<body>elementiswhereyouwritethebulkofyourHTMLcode.Inthefollowingexample, the <p> and <h1> elements between the opening and closing body tags show howHTMLisusedtodisplaytext.The<h1>elementisaheadingelementandthe<p>elementisaparagraphelement.Asthenamesimply,youusetheheadingelementtocreatetitleheadingsandtheparagraphelementtoencapsulatetextthatrepresentsaparagraph.<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>Template</title><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css">
</head><!--____________________________________________BEGINAPP--><body><h1>CreatinganInterface</h1><p>InthischapterwewillgooverHTMLandCSS</p></body><!--____________________________________________ENDAPP--></html>
UnderstandingHTMLElementsIntheHTMLspecificationthereareover100elementstochoosefrom.Eachoneofthesehasaspecificusecase.AfulllistisavailableatthefollowingURL:https://developer.mozilla.org/en-US/docs/Web/HTML/Element.
Thesheernumberofelementsmaybedauntingatfirst,butonceyouunderstandhowtouseasmallhandfulof theseelements, itbecomeseasier to learn theothers.For thepurposesof thischapter,onlythefollowingelementsareused:
When youwriteHTML andCSS, youmust understand two primary concepts. First,HTMLwebpagesconsistofahierarchyofelementsthatformanestedtree-likestructure.ThisiscalledtheHTMLDocumentObjectModel(orDOMforshort).Thefollowingdiagramreflectsthenodetreeofthepreviousexample.
Second, most elements contain opening and closing tags that are used to encapsulate otherelements. The processes of encapsulating elements within other elements and treating thecontainingelementsasboxesiscommonlyreferredtoastheboxmodel.
Thefollowingcodeemphasizestheboxmodelbyaddingelementsthatcontainotherelements.This includesacontaining<div> that encapsulates a<form>element.The<form> elementthenencapsulates<input>,<span>,and<p>(paragraph)elements.<body><h1>CreatinganInterface</h1><p>InthischapterwewillgooverHTMLandCSS</p><div><form><inputid="on-off"type="button"value="start"><span>Clicktostartoscillator</span><p>Useslidertomodifyfrequency</p><inputtype="range"></form></div></body>
ThetreestructureofthemodifiedHTMLisreflectedinthefollowingdiagram.
Therenderingofthecodelookslikethefollowingfigure.
HTMLelementscomein twocategories:block-leveland inline.Thedifferencebetween thetwoisthatblock-levelelementsdisplayverticallyandinlineelementsdisplayhorizontally.The
<div>and<span> elements are two elements that reflect these characteristics. <div> is ablock-level element and <span> is an inline element. These are both considered genericcontainerelements.Thismeans that theyconveynospecialmeaningbutareuseful tohelp lendstructuretoyourpagewhennootherelementsareappropriate.Thefollowingcodedemonstrateshowtheseelementsareinterpretedwhentheyarerenderedinthebrowser.The<hr>elementisusedsolelytocreateavisualdemarcation(horizontalline)betweenthetwoexamples.<body><span>Thistextisinsideaspan</span><span>Thistextisinsideaspan</span><hr><div>Thistextisinsideadiv</div><div>Thistextisinsideadiv</div></body>
FormandInputElements<form> is a block-level element intended to encapsulate <input> elements. <input>elementsareusedtocreatetextfields,buttons,andrangesliders.Thetypeattributeisusedtodefine the type of data the element is expected to display,which can change how the elementappearsonthepage.Soforexample,ifyousetthetypeattributetorange,itcreatesaslider.
<form><inputtype="range"></form>
Thetypeattributecomeswithabuilt-inlistofpossiblesettings,someofwhichareshowninthefollowingcode.Thevalueattributegivesthe<input>elementadefaultsetting,asshowninthefollowingdemonstrationcode(codethatisnotusedinyourfinalapplication):<body><form><p>Inputelementtypesetto"button"</p><inputtype="button"value="start"><hr><p>Inputelementtypesetto"range"</p><inputtype="range"><hr><p>Inputelementsettoa"number"</p><inputtype="number"value="44.100"><hr><p>Inputelementsettoa"text"</p><inputtype="text"value="sine"></form></body>
CSSCSSstandsforcascadingstylesheets and is the technologyused to stylewebpages andwebapplications. Like HTML, CSS does not throw errors whenwritten improperly. To check forerrors,youcanusetheW3CCSSvalidatortoolatthisURL:https://jigsaw.w3.org/css-validator/.
CSSfilesusethe.cssfileextension.TouseCSSwithanHTMLfile,youmustfirstcreateaCSS document and then connect it to yourHTML document using the <link> element in the<head>.Thefollowingexampleillustratesthisusage,whichisappliedfortheremainderofthischapter.
<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>CSSandHTML</title><linkrel="stylesheet"href="css/app.css"></head><body><h1>CreatinganInterface</h1><p>InthischapterwewillgooverHTMLandCSS</p><div><form><inputid="on-off"type="button"value="start"><span>Clicktostartoscillator</span><p>Useslidertomodifyfrequency</p><inputtype="range"></form></div></body><html>
To ensure that your CSS document is being read properly, open your HTML document inChromeandopenthedevelopertools.Ifyoumadeanerror,theconsolewillindicatethisinred.
After theCSSfile is linkedyoucanbegin toapplyCSSstyling to theHTMLelements.Forexample,ifyouwanttochangethebackgroundcolorofthepage,inyourCSSfileyouselectthebodyelementandsetthebackground-colorpropertytoacolorvalue.Thisisshowninthefollowingexamplewherethebackgroundcolorischangedtoorange.Asanalternativetousingthenameofthecolor,youcansetthecolorusingahexcolorcodevaluesuchas#ffa500orared–green–bluevaluesuchasrgb(255,165,0).
body{background-color:orange;}
TheprocedureforapplyingCSStoanelementisasfollows:
1. Select the element youwant to affect and type its name in yourCSS file. In the previousexample,thiswasbody.
2.Typeanopeningandclosingcurlybrace.Thesetwobracesarecommonlyreferredtoasacodeblock.Insidethecodeblockyouplacepropertiesandsetvaluesfollowingacolon.Inthepreviousexample,thepropertywasbackground-coloranditsvaluewasorange.Eachpropertyvaluesettingendswithasemicolon.
TheCSSspecificationincludesmanyproperties.AfulllistofpropertiesisavailableatthisURL:https://developer.mozilla.org/en-US/docs/Web/CSS/Reference.
CommentsJustlikeHTMLorJavaScript,youcanaddcommentstoyourCSSfileusingthefollowingsyntax:
/*ThisisaCSScomment*/
ElementSelectorsWhenyouselectelementsdirectly,all instancesof theelement are selectedand the sameCSSstylingisappliedtothem.Forexample,inthefollowingdemonstrationcode,every<div>onthepageisselectedandgivenabackgroundcolorofblue.div{background-color:blue;}
GroupingSelectorsIfyouwant toapplythesamestyles tomultipleselectorsyoucandosoinonelineofcodebygrouping selectors. You do this by separating each element with a comma. The followingdemonstrationcodeselectsthe<p>,<li>,and<h1>elementsandappliesthesamefontcolortoeachone.p,li,h1{color:green;//Changesfontcolorofallthreeelementstogreen}
DescendentSelectorsIfyouwanttoaccessanelementonlyifitisnestedinsideofaparticularelement,youcandothiswith descendent selectors.TheCSS syntax for this type of selector is expressed by typing theparent element, a space, and then the element you want to select. In the following code, adescendantselectorisusedtoselectall<li>elementsthatarenestedinany<div>element.Inthefollowingdemonstrationcode,thefontcolorofeach<li>elementissettoblue.divli{color:blue;}
Itisimportanttorealizethatdescendantselectorsselectallthedescendentelementsnomatterhownestedtheyare.IfthepreviousCSSexamplewereappliedtothefollowingHTMLcode,itwouldchangethefontcolorofallofthe<li>elementstoblueeventhoughtheyarenestedina<ul>element.
<div><ul><li>Item-1</li><li>Item-2</li><li>Item-3</li>
</ul></div>
ChildSelectorsChildselectorsaresimilar todescendentselectorswiththedifferencethat theselectedelementcanonlybeoneleveldeeprelativetotheparent.Achildselectorismadeusingthe“>”symbolwith the parent element on its left side and the child element on its right. The followingdemonstrationcodewillselectall<li>elementsthatarechildrenof<ul>elements.ul>li{/*dosomething*/}
classandidOften when selecting elements, you do not want to select every element of a particular type.Rather,youmightwant to select either individual instancesof elementsorgroupsof elements.Youcansingleoutanindividualelementforstylingbyusinganidentifiercalledid.Conversely,youcandesignateacollectionofelementsasagroupbyusinganidentifiercalledclass.
To singleout anelementusinganid,youmust firstdefineid as an attribute of anHTMLelement.Thesyntaxlookslikethefollowing:<divid="controls"><!—-content--></div>
InyourCSSyoucan then select this individual elementbypreceding itsidwith a hashtagcharacter.#controls{/*propertiesandvaluesgohere*/}
KeepinmindthatidnamesareintendedtobeusedonlyonceinyourHTMLandareappliedtoasingleelement!
ModifyingtheAppInterfaceIn the following code, an additional<div> is added to the page and encloses a collection ofelements. You might be wondering why <h2> is used as the first element instead of another<h1>.The reason is that thenumbervalueof theheading element is intended to represent theprecedenceoftheinformationcontainedwithinit.Thelowestnumberismostimportantandeach
highernumberislessimportant.Soforexample,theinformationcontainedinthe<h1>elementshouldtakeprecedencebecauseitconveysmoreoverallmeaningas it relates to thewebpage.Youmightnoticethatthereisasizedifferenceinthewaythebrowserrenderstheseelements.Youshould ignore this sizedifferenceand focusoncontentprecedence.Youcanalwayschange thefont sizeusingCSS tomake theseelementsanysizeyouwant, includingsetting themall to thesamesize.
Thenextelementis<ul>withanidofoscillator-list.Thiselementcontainsaseriesof<li>elements,eachgivenanidnameofaparticularwaveformtype.<ul>isanunorderedlist elementand is intended toencapsulate the<li>elements,whichare list elements.As thenameimplies,theseelementsareusedtocreatelists.<body><h1>CreatinganInterface</h1><p>InthischapterwewillgooverHTMLandCSS</p><div><form><inputid="on-off"type="button"value="start"><span>Clicktostartoscillator</span><p>Useslidertomodifyfrequency</p><inputtype="range"></form></div>
<div><h2>Waveform</h2><ulid="oscillator-list"><liid="sawtooth">sawtooth</li><liid="sine">sine</li><liid="triangle">triangle</li><liid="square">square</li></ul></div></body>
InthefollowingCSS,each<li>elementisselectedviaitsidandgivenabackgroundcolor.
#sawtooth{background-color:#336E91;}
#sine{background-color:#783d47;}#triangle{background-color:#3b3040;}#square{background-color:#b85635;}
Nowthatyouknowhowtosingleoutanelementusingidselectors,itistimetolearnhowtogroupelementsusingclassselectors.
InyourHTMLdocument,assign the two<div>elements theclassosc-controls. Thenencapsulatethefirst<h1>and<p>insideanew<div>,andplaceanother<div>elementatthebottomofthepagethatcontainsaparagraphelementwiththephrase“JavaScriptforSoundArtistsDemo”insideofit.
Youshouldhaveatotaloffour<div>elementsinthiscode,whichlookslikethis:<body><div><h1>CreatinganInterface</h1><p>InthischapterwewillgooverHTMLandCSS</p></div><divclass="osc-controls"><form><inputid="on-off"type="button"value="start"><span>Clicktostartoscillator</span><p>Useslidertomodifyfrequency</p><inputtype="range"></form></div><divclass="osc-controls"><h2>Waveform</h2><ulid="oscillator-list"><liid="sawtooth">sawtooth</li><liid="sine">sine</li><liid="triangle">triangle</li><liid="square">square</li></ul></div>
<div><p>JavaScriptforSoundArtistsDemo</p></div></body>
Toselecttheosc-controlsclassfromCSS,youmustprefacetheclassnamewithadot.
.osc-controls{/*Noticethedotselector*//*setpropertyvalues*/}
In the following code, theosc-controls class is given a border. Only themiddle two<div>elementsrespondtothesechangesbecausethefirstandthird<div>onthepagedonothavetheclassosc-controlsassignedtothem..osc-controls{border-style:solid;border-color:#BC6527;border-width:2px;border-radius:10px;}
Youmightnoticethatthepagelooksalittleawkwardbecausethestartbuttonisnowpushedupagainsttheleftedgeofitscontainer.Also,thetwo<divs>withbordersarestackeddirectlyontop of one another with no space between them. You could make this look a bit cleaner bycreatingsomespacebetweentheseelements.Todothis,youshouldhaveanunderstandingofthefollowingthreeproperties:margin,border,andpadding.
Margin,Border,andPaddingBoth block-level and inline elements have access to border, margin, and paddingproperties,althoughtheyrespondtothemdifferently.Thesethreepropertiescorrespondtothree
layersof spacearoundanelement.Theborderpropertycreatesaborderaroundanelement.Thepadding property creates a layer of space that resides inside the border. Themarginpropertycreatesalayerofspacethatresidesoutsidetheborder.
Tocreateabitofspacebetweenthetextanda<div>elementborder,includethefollowingcodeinyourCSSfile:div{padding:20px;}
Ifyouwanttoapplypaddingoramargintoonlyspecificsidesofanelement,youcandosobyusingthefollowingproperties:margin-topmargin-rightmargin-bottommargin-leftpadding-toppadding-rightpadding-bottompadding-left
Thetwooutlinesaroundthemiddle<div>elementscouldusesomespacebetweenthem.Thefollowing code creates this space by using the bottom-margin property with a value of20px..osc-controls{border-style:solid;border-color:#BC6527;border-width:2px;border-radius:10px;margin-bottom:20px;}
RemovingListElementBulletPointsThe followingcode removes thebulletpoints fromeach list itemby selectingallof the<li>elementsthataredescendentsofanelementwithanidofoscillator-listandapplyingapropertycalledlist-style-typewithavalueofnone.#oscillator-listli{/*Descendentselector*/list-style-type:none;}
Youcanremovethespacepreviouslyoccupiedbythebulletpointsbysettingthepadding-leftpropertytozeroontheparent<ul>element.#oscillator-list{padding-left:0px;}
FontSize,Style(Type),andColorAsatouch-up,thefollowingcodeselectsalltheelementsthatharbortextandsetstheirfontsizeto1.5em,whichisabitlargerthanthecurrentvalue.Anemisequivalenttoaparentelement’sfontsize,or,ifthereisnoparent,thewebbrowser’sdefaulttextsize.Formostwebbrowsers,thisvalueisabout16pixels,whichinCSSiswrittenas16px.Therefore,assumingthereisnoparentelement,2emisequivalentto32pixelsand1.5emto24pixels.p,span,li,input{font-size:1.5em;}
Thedefault font typeforChromeisTimesNewRoman.Youcanchange thefont type ifyoulike.ThefollowingcodechangesthefonttypetoArial.body{background-color:orange;font-family:"Arial";}
The fontcolorof the textdescribing thewaveform types isblack,which isdifficult to readbecausethebackgroundcolorofeach<li>isdark.Thefollowingcodechangesthetextcolorpropertytowhite.#oscillator-listli{list-style-type:none;color:white;}
CenteringBlock-LevelElementsIfyouwanttocenterablock-levelelement,youcandosobysettingitswidthtoavaluesmallerthanitscontainingelementandapplyingamarginpropertywithavalueof0auto.Thissetsthe left and right margin values to automatically extend to the boundaries of the container,centeringtheelement.Inthefollowingcode,a<div>withaclassofapplicationcontainsalltheHTMLwithinthebodyelement.ItsCSSissettoafixedwidthandcenteredbyspecifyingmargin:0auto.<body><divclass="application"><div><h1>CreatinganInterface</h1><p>InthischapterwewillgooverHTMLandCSS</p></div><divclass="osc-controls"><form><inputid="on-off"type="button"value="start"><span>Clicktostartoscillator</span><p>Useslidertomodifyfrequency</p><inputtype="range"></form></div><divclass="osc-controls"><h2>Waveform</h2><ulid="oscillator-list"><liid="sawtooth">sawtooth</li><liid="sine">sine</li><liid="triangle">triangle</li><liid="square">square</li></ul></div><div>
<p>JavaScriptforSoundArtistsDemo</p></div></div></body>
TheCSSforthenewlycreateddivisasfollows..application{width:550px;margin:0auto;}
Asafinalstep,removethefirst<p>elementandreplacethetextofthe<h1>elementwiththetitleOscillatorGenerator.<divclass="application"><div><h1>OscillatorGenerator</h1></div><divclass="osc-controls"><form><inputid="on-off"type="button"value="start"><span>Clicktostartoscillator</span><p>Useslidertomodifyfrequency</p><inputtype="range"></form></div><divclass="osc-controls"><h2>Waveform</h2><ulid="oscillator-list"><liid="sawtooth">sawtooth</li><liid="sine">sine</li><liid="triangle">triangle</li><liid="square">square</li></ul></div><div>
<p>JavaScriptforSoundArtistsDemo</p></div></div>
SummaryInthischapter,youcreatedtheuserinterfaceforasmallapplication.Inthenextchapter,youwilladdJavaScriptcodetomaketheapplicationfullyfunctional.
9DOMProgrammingwithJavaScript
ThischaptershowsyouhowtoaddJavaScripttoCSSandHTML.Bytheendofthechapter,youwillhavecreatedafullyfunctioningapplicationwithinteractivecontrols.
HowDoesJavaScriptCommunicatewiththeDOM?
TheDOM(DocumentObjectModel)containsacollectionofJavaScriptpropertiesandmethodsthatallowsyou tomanipulateHTMLelementsand towritecode that responds touser-invokedactionssuchasmouseclicksandformsubmissions.Typically,whenwritinganapplication,youwant toaskyourself two things.Thefirst is,whatdoyouwantorexpect theuser todo? Thesecondis,whatshouldhappeninresponsetouseractions?Soforexample,thefollowingcodecontains a play button, and when a user clicks it the browser runs a built-in function calledalert.Thisfunctiondisplaysapop-upwiththemessage:Youclickedplay.
HTML<body><inputid="play-button"type="button"value="PLAY"></body>
JavaScriptwindow.onload=function(){varplayButton=document.getElementById("play-button");playButton.addEventListener("click",function(){alert("Youclickedtheplaybutton");
});};
The first lineof JavaScript,window.onload=function(){}, restricts the code inthefunctionscopefromrunninguntilalloftheHTMLcodehasloaded.IftheJavaScriptweretoloadbeforetheHTML,thenanyJavaScriptintendedtoaffectHTMLelementsorrespondtousereventslikemouseclickswouldeithernotberecognizedorwouldbeonlypartiallyrecognized.Theresultineitherofthesecasesisnonworkingcode.
ThenextlineselectsaDOMelementwithanidofplay-buttonandstoresitinavariablecalledplayButton. This is done using getElementById, a method of the documentobject.
Thedocument object is not part of the core JavaScript language. Instead, it is an objectprovidedbytheDOMAPI,makingitpartofthewebbrowser.varplayButton=document.getElementById("play-button");
ThenextlineofcodeappliestheeventListenermethodtotheplayButton.playButton.addEventListener("click",function(){alert("Youclickedtheplaybutton");});
TheplayButton.addEventListenermethodwaits forauser toapplyanactionandtheninvokesacallbackfunction.Inthiscase,theactionisamouseclickandisspecifiedinthefirstargumentof thefunction.Thesecondargumentis thecallback.Thecallbackrunswhentheuserclicksontheelementwiththeidofplay-button,whichinvokesalert().
The JavaScript DOMAPI contains many methods and properties. In this chapter, only thefollowingoftheseareused.
BuildingtheApplication
In thepreviouschapter,youbuiltauser interfaceusingCSSandHTML.YouarenowgoingtowritethecodetomakethisaworkingJavaScriptapplication.Togetstarted,createacopyofthefinalprojectinChapter8andmakesureyoucreateafolderthatcontainsafilenamedapp.js.Yourdirectorystructureshouldlookliketheoneinthefollowingimage.
Intheapp.jsfile,makesureyouhavestrictmodeenabled.AllJavaScriptcodeiswrittenbelowtheusestrictstring."usestrict";
Setyourindex.htmlfiletoreferencetheapp.jsfile.<head><metacharset="UTF-8"><title>CSSandHTML</title><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"></head>
HowtoTriggeranOscillatorbyClickingaButtonTheuserinterfaceyoucreatedinthepreviouschaptercontainedabuttonwiththeidofon-off.Youarenowgoingtowritecodetostartanoscillatorplayingwhenthisbuttonisclicked."usestrict";varaudioContext=newAudioContext();window.onload=function(){varonOff=document.getElementById("on-off");onOff.addEventListener("click",function(){varosc=audioContext.createOscillator();osc.type="sawtooth";osc.frequency.value=300;osc.connect(audioContext.destination);osc.start(audioContext.currentTime);});};
Althoughthiscodestartstheoscillatorplaying,itdoesnotstopitfromplaying.ThefollowingchangesimplementthestopfeaturebyaddingaconditionalstatementtoaddEventListener
tocheckwhetheravariablenamedosc isset tofalse.Ifosc isfalse,anoscillator iscreatedandassignedtoit.ThismakestheBooleanvalueoftheoscvariabletrue,andthestartmethodisinvoked,allowingtheoscillatortoplay.IftheuserclickstheStartbuttonagain,theconditionalstatementseesthattheoscvariablehastheBooleanvaluetrueandrunsthecodeintheelsebranch.Thisstopstheoscillatorfromplayingandresetsosctofalse.
"usestrict";varaudioContext=newAudioContext();window.onload=function(){varonOff=document.getElementById("on-off");/*_________________________________________BEGINsetinitialoscstatetofalse*/
varosc=false;
/*_________________________________________ENDsetinitialoscstatetofalse*/onOff.addEventListener("click",function(){/*_____________________________________BEGINConditionalstatementtocheckifoscisTRUEorFALSE*/if(!osc){/*_________________________Isoscfalse?Ifsothencreateandassignoscillatortooscandplayit.*/
osc=audioContext.createOscillator();osc.type="sawtooth";osc.frequency.value=300;osc.connect(audioContext.destination);osc.start(audioContext.currentTime);
/*_________________________________Otherwisestopitandresetosctofalsefornexttime.*/}else{
osc.stop(audioContext.currentTime);osc=false;}/*_____________________________________ENDConditionalstatementtocheckifoscisTRUEorFALSE*/});};
TogglingtheStart/StopTextThough the code in the previous example works, the following feature makes it more user-friendly:Programthebuttontextandassociatedspanelementtexttodisplaythewordsstartorstopdependingonwhethertheoscillatorisplayingornot.
Youcanimplementthisfeatureasfollows:"usestrict";varaudioContext=newAudioContext();window.onload=function(){varonOff=document.getElementById("on-off");varspan=document.getElementsByTagName("span")[0];
/*_________________________________________BEGINsetinitialoscstatetofalse*/varosc=false;/*_________________________________________ENDsetinitialoscstatetofalse*/
onOff.addEventListener("click",function(){/*_____________________________________BEGINConditionalstatementtocheckifoscisTRUEorFALSE*/
if(!osc){/*_________________________Isoscfalse?Ifsothencreateandassignoscillatortooscvariableandplayit.*/osc=audioContext.createOscillator();osc.type="sawtooth";osc.frequency.value=300;osc.connect(audioContext.destination);osc.start(audioContext.currentTime);onOff.value="stop";span.innerHTML="Clicktostoposcillator";/*_________________________________Otherwisestopitandresetosctofalsefornexttime.*/}else{
osc.stop(audioContext.currentTime);osc=false;onOff.value="start";span.innerHTML="Clicktostartoscillator";}
/*_____________________________________ENDConditionalstatementtocheckifoscisTRUEorFALSE*/});};
ThiscodeintroducesanewDOMmethodcalledgetElementsByTagNameaswellastwonew DOM properties: innerHTML and value. The getElementsByTagName methodallowsyoutoselectacollectionofelementsonthepagebytagname.Youcanthenspecifyanindividualelementusingarray-styleindexselectors.Theindexselectionrepresentstheorderoftheelementonthepage,withthefirstelementstartingat0.Toselectthefirstspanelementonthepage, you specify getElementsByTagName(“span”)[0]. If there are several spanelementsandyouwanttoselectthethirdonefromthetopofthepage,youspecifytheindexas2,andtheselectorlookslikethis:
document.getElementsByTagName("span")[2]
ItisimportanttounderstandthatDOMelementsarenotarrays,eventhoughthenotationusedtoselectthemissimilartothatusedforarrays.DOMelementsarereferredtoasnodes.
ProgrammingtheFrequencySliderYouarenowgoingtoaddfunctionality to thefrequencyslider.Thefollowingcodeshowshowyoucapturethevalueofthefrequencysliderwhentheoscillatorisclicked.Thisisdoneusingthevalueproperty.ThevalueisthenstoredinavariablenamedfreqSliderValandrepresentsthe frequency of the oscillator. Additionally, thefreqSliderVal is logged to the console,allowingyoutoseechangesmadetoit."usestrict";varaudioContext=newAudioContext();window.onload=function(){varonOff=document.getElementById("on-off");varspan=document.getElementsByTagName("span")[0];
/*_________________________________________BEGINsetinitialoscstatetofalse*/varosc=false;/*_________________________________________ENDsetinitialoscstatetofalse*/onOff.addEventListener("click",function(){/*_____________________________________BEGINConditionalstatementtocheckifoscisTRUEorFALSE*/varfreqSliderVal=document.getElementsByTagName(“input”)[1].value;
console.log(freqSliderVal);
if(!osc){/*_________________________Isoscfalse?Ifsothencreateandassignoscillatortooscvariableandplayit.*/osc=audioContext.createOscillator();osc.type="sawtooth";osc.frequency.value=freqSliderVal;osc.connect(audioContext.destination);osc.start(audioContext.currentTime);onOff.value="stop";span.innerHTML="Clicktostoposcillator";/*_________________________________Otherwisestopitand
resetosctofalsefornexttime.*/}else{
osc.stop(audioContext.currentTime);osc=false;onOff.value="start";span.innerHTML="Clicktostartoscillator";}/*_____________________________________ENDConditionalstatementtocheckifoscisTRUEorFALSE*/});};
ChangingtheFrequencyinRealTimeThecodeinthepreviousexampleworks,butthereisacost.Tohearthefrequencychanges,theusermustturntheoscillatoroff,movethefrequencyslider,andthenstarttheoscillatoragain.Youcouldprovideaseamlessexperienceiftheusercouldheartheeffectinrealtimewhilemovingtheslider.To implement this,youcanusesetInterval() tocheck for statechangesof therangesliderandthenapplythemtotheosc.frequencyvalue.
The purpose of setInterval() is to invoke a function repeatedly at a specified timeinterval.Inthefollowingcode,setInterval() takestwoarguments.Thefirstisacallbackand the second is a number that represents amillisecond interval value (in this case, 50ms).WhensetInterval()runs,thecallbackisinvokedrepeatedlyatthetimeintervalspecifiedin thesecondargument.ThefreqSliderValvariablehasbeenplacedoutside the scopeofboththeonOff.addEventListenerandsetIntervalmethodssothatbothofthemhaveaccesstoit.ThesetIntervalmethodcontainsaconditionalthatcheckstoseewhetheroscisfalse,andifsodisplaysthemessage“Oscillatorisstopped.Waitingforoscillatortostart,”intheconsole.Themomenttheoscillatorstarts,setInterval() reassignsfreqSliderValto therespective<input> rangevalueandassigns thatvalue toosc.frequency.value.BecausesetInterval()doesthisin50-msincrements,itcreatesnearreal-timechangeintheoscillatorfrequencywhenyoumovethefrequencyslider."usestrict";varaudioContext=newAudioContext();window.onload=function(){varonOff=document.getElementById("on-off");varspan=document.getElementsByTagName("span")[0];/*_________________________________________BEGINsetinitialoscstatetofalse*/
varosc=false;/*_________________________________________ENDsetinitialoscstatetofalse*/
/*_________________________________________BEGINsetinitialfrequencyvalue*/varfreqSliderVal=document.getElementsByTagName("input")[1].value;/*_________________________________________ENDsetinitialfrequencyvalue*/
/*_________________________________________BEGINcheckrangeslidervalueandsetfrequencyofoscillator*/
setInterval(function(){
if(!osc){
console.log("Oscillatorisstopped.Waitingforoscillatortostart");
}else{
freqSliderVal=document.getElementsByTagName("input")[1].value;osc.frequency.value=freqSliderVal;console.log("Oscillatorisplaying.Frequencyvalueis"+freqSliderVal);}
},50);
/*_________________________________________Endcheckrangeslidervalueandsetfrequencyofoscillator*/
onOff.addEventListener("click",function(){
/*_____________________________________BEGINConditionalstatementtocheckifoscisTRUEorFALSE*/
if(!osc){/*_________________________Isoscfalse?Ifsothencreateandassignoscillatortooscvariableandplayit.*/osc=audioContext.createOscillator();osc.type="sawtooth";osc.frequency.value=freqSliderVal;osc.connect(audioContext.destination);osc.start(audioContext.currentTime);onOff.value="stop";span.innerHTML="Clicktostoposcillator";/*_________________________________Otherwisestopitandresetosctofalsefornexttime.*/}else{
osc.stop(audioContext.currentTime);osc=false;onOff.value="start";span.innerHTML="Clicktostartoscillator";}
/*_____________________________________ENDConditionalstatementtocheckifoscisTRUEorFALSE*/
});};
ChangingWaveformTypesYouarenowgoingtowritecodetoallowuserstoclickoneofthefourwaveformsonthepageandsettheoscillatortoplaytheselectedwaveform.
Todothis,usetheeventListener()methodtocapturetheidoftheelementclickedbytheuser.Becausetheidofeach<li>isthenameofawaveform,youmustassignthisidtotheosc.typeproperty.Youwantuserstobeabletoupdatethewaveformtypewithouthavingtorepeatedlyrestarttheoscillator,andyoucandothissimilarlytothefrequencysliderchangesinthepreviousexample.
In the following code, you create a variable named selectedWaveform to give theoscillatoradefaultwaveformtypeandtostoreanyselectedwaveformchanges.varselectedWaveform="sawtooth";
Thisvalueisthenassignedtoosc.type.
if(!osc){osc=audioContext.createOscillator();osc.type=selectedWaveform;osc.frequency.value=freqSliderVal;osc.connect(audioContext.destination);osc.start(audioContext.currentTime);onOff.value="stop";span.innerHTML="Clicktostoposcillator";}
Next, create a variable named waveformTypes and assign it the result of callinggetElementsByTagName().ThewaveformTypesvalueisusedtoselectoneofthefour<li>elementsonthepage.varwaveformTypes=document.getElementsByTagName('li');
Next,createafunctionnamedselectthatisusedasacallbackforaseriesofeventlistenersusedtoselecttheidofthe<li>clickedbytheuser.functionselect(){selectedWaveform=document.getElementById(this.id).id;
console.log(selectedWaveform);//Outputsid}
Next,aforloopisusedtoexamineeach<li>nodeandassignaneventlistenertoeachone.Eacheventlistenerissettorespondtoaclickeventthatinvokesthecallbackfunctionselect.Whentheselectfunctionruns,itcapturestheidofthe<li>elementclickedbytheuser.ThisidisthenstoredintheselectedWaveformvariable.
for(vari=0;i<waveformTypes.length;i++){waveformTypes[i].addEventListener('click',select,false);}
CompletedCodewithWaveformSelection"usestrict";varaudioContext=newAudioContext();window.onload=function(){varonOff=document.getElementById("on-off");varspan=document.getElementsByTagName("span")[0];varosc=false;varfreqSliderVal=document.getElementsByTagName("input")[1].value;
/*_________________________________________BEGINsetselectedwaveformtypevalue*/
varselectedWaveform="sawtooth";/*_________________________________________ENDsetselectedwaveformtypevalue*/
/*_________________________________________BEGINselectall<li>elements*/varwaveformTypes=document.getElementsByTagName('li');/*_________________________________________ENDselectall<li>elements*/
/*_________________________________________BEGINcallbacktoselect<li>byidandassignidnametoselectWaveform*/functionselect(){selectedWaveform=document.getElementById(this.id).id;console.log(selectedWaveform);}
/*_________________________________________ENDcallbacktoselect<li>byidandassignidnametoselectWaveform*/
/*_________________________________________BEGINloopthroughall<li>elementsandsetaclickeventListeneronthem*/
for(vari=0;i<waveformTypes.length;i++){waveformTypes[i].addEventListener('click',select);}
/*_________________________________________ENDloopthroughall<li>elementsandsetaclickeventListeneronthem*/
setInterval(function(){
if(!osc){
console.log("Oscillatorisstopped.Waitingforoscillatortostart");
}else{
freqSliderVal=document.getElementsByTagName("input")[1].value;osc.frequency.value=freqSliderVal;console.log("Oscillatorisplaying.Frequencyvalueis"+freqSliderVal);osc.type=selectedWaveform;}
},50);
onOff.addEventListener("click",function(){
if(!osc){osc=audioContext.createOscillator();osc.type=selectedWaveform;osc.frequency.value=freqSliderVal;osc.connect(audioContext.destination);osc.start(audioContext.currentTime);onOff.value="stop";span.innerHTML="Clicktostoposcillator";}else{
osc.stop(audioContext.currentTime);osc=false;onOff.value="start";span.innerHTML="Clicktostartoscillator";}});};
GivinganOutlinetotheSelectedWaveformTypeWhenauserselectsawaveformtype,thereisnovisualcuethatidentifiesthetypeselected.Thefollowingcodeaddsawhiteoutlinetotheselectedwaveform.
IntheCSSfile,createaclassnamedselected-waveformandgiveitanoutlinepropertywith a width of 2 pixels and the color white. Then, add this class dynamically to the<li>elementthatcorrespondstotheselectedwaveformtype.Toremovetheselectedwaveformclassofthepreviouslyselectedelement,useaforlooptoexamineallofthe<li>elementsandinvokeclassList.remove("selected-waveform")oneachone.
IntheCSSfile,createtheclass..selected-waveform{outline:2pxsolidwhite;}
IntheJavaScriptfile,addthefollowingcodetotheselectfunction.functionselect(){
//_____________________________________BEGINselectelementbyidvarselectedWaveformElement=document.getElementById(this.id);//_____________________________________ENDselectelementbyid
selectedWaveform=document.getElementById(this.id).id;console.log(selectedWaveform);
/*_____________________________________BEGINremoveanypreviouslyaddedselected-waveformclasses*/
for(vari=0;i<waveformTypes.length;i+=1){waveformTypes[i].classList.remove("selected-waveform");}/*_____________________________________ENDremoveanypreviouslyaddedselected-waveformclasses*/
/*_____________________________________BEGINaddtheselected-waveformclasstotheselectedelement*/
selectedWaveformElement.classList.add("selected-waveform");/*_____________________________________ENDaddtheselected-waveformclasstotheselectedelement*/}
SummaryInthischapter,youlearnedhowJavaScriptinteractswiththeDOM.Inthenextchapter,youwilllearnthebasicsofalibrarynamedJQuerythatmakesDOMprogrammingwithJavaScripteasier.
10SimplifyingDOMProgrammingwithJQuery
Inthepreviouschapter,youlearnedhowJavaScriptinteractswiththeDOM.Inthischapter,youwill learnhowtosimplify theprocessofadding interactivecomponents toyourapplicationbyusingalibrarycalledJQuery.TheobjectiveofthischapterisnottoteachyoutheentireJQueryAPI, but to give you the foundational knowledge tomake JQuery a part of your programmingtoolkit.YoucanfindtheJQueryAPIatthisURL:https://api.jquery.com/.
WhatIsJQuery?JQueryisalibrarywritteninJavaScriptintendedprimarilyforDOMmanipulation.Alibraryisacollection of preassembled code pieces designed to make a particular group of tasks easier.JQuery contains a large collection ofmethods and properties that can be used individually orcombinedtohelpeasethecomplexityofJavaScriptDOMprogramming.
JQuerySetupYoucansetupJQueryinoneoftwoways.Thefirstistodownloadthelibraryandreferenceitfrom anHTML file. The second is to reference it from a content delivery network (CDN forshort).ACDNisaserviceaccessiblethroughtheWorldWideWebwhereyoucanreferencecodelibrariesandotherfiles.ThedownsideofusingaCDNisthatyouwillalwaysneedaworkingInternetconnectiontoaccessit.
ReferencingJQueryDirectly
ToreferenceJQuerydirectly,firstdownloadthelibraryatthisURL:http://jquery.com/.Next,usethefollowingcodeasanexampleofhowtoreferencethelibrary.<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title></title><scripttype="text/javascript"src="js/jquery-2.1.4.min"charset="utf-8"></script><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"></head><!--_____________________________________________BEGINAPP--><body>
</body><!--_____________________________________________ENDAPP--></html>
UsingJQueryfromaCDNThe following code references JQuery from a Google CDN library collection at this URL:https://developers.google.com/speed/libraries/.<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title></title><scripttype="text/javascript"src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.js"charset="utf-8"></script><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"></head><!--_____________________________________________BEGINAPP--><body>
</body><!--_____________________________________________ENDAPP--></html>
Inthepreviouschapter,yourJavaScriptcodewasencapsulatedinthefollowingfunction:window.onload=function(){//codegoeshere};
Thiswasdonetoensurethat thecodeloadsafter thebrowserrenders theHTMLdocument.JQuerycomesbundledwithafunctionthatdoesthesamethingwithslightlydifferentsyntax.$(function(){//codegoeshere});
HowtoUseJQueryThemostfundamentalusecasesforJQueryrequireknowledgeoftwothings.ThefirstishowtoselectanHTMLelement.Thesecondishowtodosomethingwiththatelement.
SelectingHTMLElementsThefollowingcodeshowshowtoselectanHTMLelement.$("div")//thisselectsalldivelementsonthepage
The syntax for element selectors always begins with a dollar sign, followed by twoparentheses.Youplace theelementwrapped inquotes inside theparentheses. JQueryselectorsborrowfromCSSselectorsyntax.IfyouknowhowtoselectelementsusingCSS,youcanquicklylearntoselectelementsinJQuery.ThefollowingareafewexamplesofCSSselectorsandtheirJQuerycounterparts.
StoringDOMSelectorsasVariablesInsomecircumstances,youmightfinditaestheticallypreferabletostoreyourDOMselectorsasvariables. The following code is a modified version of the previous example with the divselectorstoredinavariable.ThevariableisprecededbyadollarsigntoemphasizethatitisaJQuery selector. Storing the DOM selector as a variable has the same effect as selecting theelementdirectly.$(function(){var$transportControl=$("div");
});
UsingMethods
After you have selected an element, you can modify it in some way using JQuery’s built-inmethods.JQuerycomeswithalargecollectionofmethods;however,inthischapterweareonlygoingtousethefollowingmethods:
Thefollowingisanexampleofusingcss()tomodifythelookofanelement.css()canbeused either to change a single property or to set multiple properties using an object as anargument.Anexampleofbothusecasesisgiveninthefollowingcode:
HTML<div>Play</div><div>Stop</div><div>Rewind</div><div>FastForward</div><div>Pause</div>
Tochangeasingleproperty:
JQuery/JavaScripttoChangeaSingleProperty$(function(){$("div").css("background-color","orange");});
Tochangemultipleproperties:
JQuery/JavaScripttoChangeMultipleProperties$(function(){$("div").css({backgroundColor:"orange",width:"100px",borderStyle:"solid"
});});
MethodChainingIfyouwanttoapplymultiplemethodstoanelement,youcanconnecttheminsuccessionsothattheyareinvokedoneafteranother.Thisiscalledmethodchaining.
Inthefollowingcode,allofthedivelementshavetheirCSSdisplaypropertysettonone.JQuery is used to select the div elements and set their CSS properties using css(). ThefadeInmethodisthenchainedtoeachdiv,sothateverydivon thepagefades inover thecourseof1second(1000milliseconds).ThefirstargumentoffadeIn()isthedurationoftheanimationinmilliseconds.WhenfadeIn()completes,thefadeOutmethodisinvoked,whichfadesoutalldivelementsoverthecourseof1second.
HTML<div>Play</div><div>Stop</div><div>Rewind</div><div>FastForward</div><div>Pause</div>
CSSdiv{display:none;}
JQuery/JavaScript$(function(){$("div").css({backgroundColor:"orange",width:"100px",borderStyle:"solid"}).fadeIn(1000).fadeOut(1000);//exampleofmethodchaining});
The following HTML code contains an input element with its type attribute set tobutton.This isselectedwithJQueryandset torespondtoclickeventsviaanevent listener.Themethodusedforthisison(),whichtakestwoarguments.Thefirstargumentisastringthatdefinestheeventtypeandthesecondisacallbackthatisinvokedwhentheeventisfired.
HTML<inputtype="button"value="Play">
JQuery/JavaScript$(function(){$("input").on("click",function(){//clickeventlisteneralert("Youclickedplay");});});
ThethisKeywordThe this keyword in JQuery can be used as a shorthand for the currently selected DOMelement. The following HTML code contains three input elements. Using JQuery, these threeelementsareassignedaclickevent listener.Whenauserclicksan inputbutton, the$(this)selectorisusedtosingleouttheindividualelementtheuserclicked.Thevalmethodreturnsthevalueattributeoftheclickedelement.
HTML<inputtype="button"value="Play"><inputtype="button"value="Pause"><inputtype="button"value="Stop">
JQuery/JavaScript$(function(){$("input").on("click",function(){/*assigneventlistenertoallinputelements*/console.log($(this).val());/*usethethiskeywordtoaccesstheelementclickedandreturnitsvalueproperty*/});});
RefactoringtheOscillatorPlayertoUseJQueryNow that you understand some JQuery basics, you are going to refactor the application youcreated in thepreviouschapterbyreplacing thebuilt-inJavaScriptDOMmethodswithJQueryselectorsandmethods.
Copythecompletedcodefromthepreviouschaptertoanewdirectory.Inyourindex.htmlfilereferencetheJQuerylibrary.<head><metacharset="UTF-8"><title>CSSandHTML</title><scripttype="text/javascript"src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.js"charset="utf-8"></script><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"></head>
Replacetheapp.jsfilewithanewemptyfilewiththesamename.Intheoldapplication,youusedthisfunctiontoencapsulateyourcode:
window.onload=function(){}
In the new version of your code, make sure you replace window.onload with theequivalentJQueryfunction.Alsoput"usestrict"andtheAudioContextinstantiationatthetopofthefile,asinthefollowingexample:"usestrict";varaudioContext=newAudioContext();$(function(){//allcodewillgohere});
Next,modifythefirstthreevariablesoftheJavaScriptfiletouseJQueryselectors.
WithoutJQuery
WithJQuery
This code uses $("#on-off") to select the oscillator start or stop button by id. It isdenotedbythehashselector.Youthenuse$("span")toselectthespanthatcontainsmessagetext.Thisselectionisdonebyelementand,becausethereisonlyonespanelementonthepage,youdonotneedtobemorespecific.Lastly,$("input").eq(1).val()isusedtoselecttherangeslidervalueofthesecondinputelementonthepage,whichisstoredinavariablenamed$freqSliderVal.Thisisdonebymakingageneralelementselectionforallinputelementsandspecifyingthesecondoneonthepagewiththeeq(1)method.Theeq()methodenablesselectionof elements by indexwith its argument being the indexvalue.Once the correct inputelementisselected,val()isusedtogetitsvalueattribute.
SettingUptheEventListenerfortheUser-SelectedListElement
In theoldapplication, theuser interfacecodeforoscillatorselectionwasabitcomplex.First,youneededtocreatealoopthatattachedaclickeventlistenertoall<li>elements.Thenyoucreatedafunctionnamedselecttobeinvokedasacallbackforeachofthoseeventlisteners.Whentheuserclickedan<li>element, theselect()callback looped throughevery<li>and removed any class identifiers titled selected-waveform. It then assigned theselected-waveformclasstoonlytheclickedelement.
WithJQuery,muchofthiscomplexitycanbeavoided.ThefollowingimagesshowthecontrastbetweentheoldversionandarefinedJQueryimplementation.
EventListenerwithoutJQuery
EventListenerwithJQuery
TherefactoredJQuerycodeassignsaclickeventlistenertoall<li>elements.Whentheuserclicks an <li> element, its id is stored in a variable named selectedWaveform.selectedWaveformisreferencedinahigherscopeandisusedlatertosettheoscillatortype.
TheremoveClass()method isused to remove theselected-wavefrom class fromall<li>elements.Thelastlineofcodeuses$(this)toselectthespecific<li>theuserclickedandinvokesaddClass()togiveittheclassselected-waveform.
ModifyingtheCodeinsetIntervalThe only modification you need to make to setInterval is the replacement offreqSliderValwithaJQueryDOMselector.
setIntervalMethodwithoutJQuery
setIntervalMethodwithJQuery
The remaining changes require you tomodify the name of theonOff selector variable to$onOffandreplacetheaddEventListenerwiththeon()methodsettorespondtoclickevents. Then rename the freqSliderVal to $freqSliderVal, replace the
span.innerHTMLwith$messageText.text(),andlastlyreplaceonOff.valuewiththeJQueryequivalentof$onOff.val().
onOffMethodwithoutJQuery
$onOffSelectorwithJQuery
SummaryIn this chapter, you learned the basics of using JQuery for DOM manipulation. You alsorefactoredthecodeinthepreviouschaptertocontrastthedifferencebetweenworkingwithand
withoutJQuery.Inthenextchapter,youwilllearnhowtoimportandplaybackaudiofileswiththeWebAudioAPI.
11LoadingandPlayingAudioFiles
Inthischapter,youwilllearnthebasicsofworkingwithaudiofiles.Thisincludeshowtoload,play,andrunaudiofilesthroughthenodegraphtotakeadvantageofitsbuilt-ineffects.
PrerequisitesTo load and play back audio files, you must be running a web server. Chapter 1 gives youinstructionsabouthowtointegrateawebserverwithSublimeTextbyinstallingapackagecalledSublimeServer.Youraudiofilesarereferencedfromthedirectorythewebserverispointingto,whichyoucansetupasfollows:
1.Createafolderonyourdesktopwithanewprojecttemplate.
2. Start the Sublime Text web server by selecting Tools > SublimeServer > StartSublimeServer.
3.Openthesidebar(ifitisn’talreadyopen)byselectingView>SideBar>ShowSideBar.
4.Dragthetemplatefoldertothesidebarpanel.
5.Openawebbrowserandenter:http://localhost:8080intheURLfield.
6. Click the link to the template folder. An empty screen is displayed because the initialtemplateisempty.
For this exercise, youwill need an audio file that is short and preferably ofMP3 format (forcompatibility issues). The file referenced in the example code is snare.mp3. Create adirectoryinyourtemplatefolderandnameitsounds,thencopyyourMP3filethere.
TheTwoStepstoLoadinganAudioFile
Loadinganaudiofileisdoneintwosteps:
1.StoretheaudiofileinabufferusingtheXMLHttpRequestobject.
2.DecodethebufferwithdecodeAudioData.
In the first step, you use the built-in browser object namedXMLHttpRequest to store thecollectedaudiofileinabuffer.ThisobjectispartofthewebbrowserandisindependentoftheWebAudioAPI.Abufferisasmallpieceofinternalmemoryusedtostoredatasothatitcanbeaccessed quickly. Storing the file this way provides low latency playback and the ability tomodifytherawwaveformdata,whichisusefulforsomeapplications.
Inthesecondstep,youusethedecodeAudioDatamethodtodecode theaudiofilebufferyoucreatedinthefirststep.AftertheWebAudioAPIhasreadanddecodedtheaudiodata,youcanassigntheobjecttoavariableandreferenceitinthenodegraphforplayback.
Thefollowingexampleshowshowyou loadasingleaudiofile,afterwhichyoucanplay itbackbyclickingonthebrowserwindow.Therestofthischapterisdedicatedtoexplainingeachlineinthisexampleandhowallthelinesworktogether.varaudioContext=newAudioContext();varaudioBuffer;vargetSound=newXMLHttpRequest();getSound.open("get","sounds/snare.mp3",true);getSound.responseType="arraybuffer";
getSound.onload=function(){audioContext.decodeAudioData(getSound.response,function(buffer){audioBuffer=buffer;});};
getSound.send();
functionplayback(){varplaySound=audioContext.createBufferSource();playSound.buffer=audioBuffer;playSound.connect(audioContext.destination);playSound.start(audioContext.currentTime);}window.addEventListener("mousedown",playback);
TheXMLHttpRequestObjectThe first step to importing an audio file is to create a newXMLHttpRequest object. Thisobject allowsyou to importdataover thehttp protocol,which is the sameprotocol used toloadwebpages.Thisdatacanthenbestoredinvariousforms.Thecodetocreatetheobjectisasfollows:vargetSound=newXMLHttpRequest();
The XMLHttpRequest function invocation returns an object literal that is stored in avariablenamedgetSound.Thisisdoneusingthenewkeyword.Thenewkeywordwillnotbecovereduntilthenextchapter,butdon’tworry.Fornow,theimportantthingistounderstandthatXMLHttpRequestisafunctionthatreturnsanobject.Thisobjectcontainsalargecollectionofbuilt-inpropertiesandmethods.Forloadingsoundfiles,youneedfiveofthesemethods:
■open
■responseType
■onload
■response
■send
The next line of code uses the open method to fetch the audio file. This method has threearguments.vargetSound=newXMLHttpRequest();getSound.open("get","sound/snare.mp3",true);
To clearly understand the purpose of the first argument requires a brief explanation of acommandcalledagetrequest.
getRequestsWhen you type aURL into aweb browser and “go” to awebsite, theweb browser does notactually go anywhere.What actually happens is the browser issues a command to thewebsiteserver that initiates a download of theHTML content and other files needed to view it. Thiscommandiscalledagetrequest.Thebeautyofgetrequestsisthatyoucanusethemoutsidethecontextof typingaURLintoabrowser. Inotherwords,youcanwritecode torunget requestsbehind the scenes. This is how XMLHttpRequest is used to pull audio files into yourapplication—andwhythefirstargumentoftheopenmethodis“get”.
The secondargument to theopenmethod is thepath to the file youwant to fetch.For thisexample,anMP3filenamedsnare.mp3isimported.
AWordonAudioFileTypeCompatibilityIt is important to understand that audio file type compatibility is dependent on which webbrowseryouuse.Ifyouwantyourapplicationtobecompatiblewithmultiplewebbrowsers,youhavetoincludemultipleaudiofilesofdifferentformatsandwriteconditionalcodetodeterminewhatformattousebasedontherenderingbrowser.Thethreemostpopularaudiofileformatsfor
web browsers areWAV,OGG, andMP3.An audio file format compatibility chart for variousbrowsersisavailablehere:http://caniuse.com/#search=audio%20format.
The thirdargument to theopenmethoddetermineswhether theopenoperation isdone inasynchronousorasynchronousmanner.Thetruevalueselectstheasynchronoussetting,whereasfalse selects the synchronous setting.Understanding thedifferencebetween synchronous andasynchronouscodeexecutionisanin-depthtopicandrequiressomeexplaining.
SynchronousversusAsynchronousCodeExecutionWhenthebrowserexecutescode,itdoessofromtoptobottom.Asaresult,afunctionthattakesalongtimetoexecutecreatesanoticeabledelayintheprogramitself.Thisisbecausethecodeisexecutingsynchronously.
WhenyouusetheXMLHttpRequestobject to retrievedatasynchronously fromaserver,thetimedelaybetweenmakingthegetrequestandwhentheactualdataisreturnedcancreateanoticeabledelayintheexecutionofyourprogram.Thisdelayisparticularlynoticeablewhenyouloada largeaudiofileand thenhave towait for itsentirecontents to load intomemorybeforeyourprogramcontinues.
Delays in execution arewhydoing suchoperations synchronously is discouraged anddoingthemasynchronouslyispreferred.Workingasynchronouslyletsyouruntheopenmethodwhileimmediately allowing your program to continue executing to completion. In themeantime, theaudiofilecontinuestoloadbehindthescenes,regardlessofhowlongittakestocomplete.Whentheaudiofileisavailable(whenitisdoneloading),youcanuseit.Whencodeisexecutedinthismanner,wesaythatitisnonblocking.Ofcourse,thedownsideofthisisthatifyouhaveanaudiofileloadinganditistakingalongtime,theuserofyourprogrammightbewonderingwhynothingis playing even though the page has rendered! This problem can be remedied by presenting amessagetouserstowarnthemthattheywillhavetowaitfortheaudiofiletofinishloading.Inthemeantime,theycanexploreotherpartsofyourapplication.
ThenextlineofcodesetsapropertycalledresponseTypetoavalueofarraybuffer.TheresponseTypepropertydefineshowthedatayouareimportingismadeavailabletoyourprogram.Generally,theXMLHttpRequestobjectisusedtofetchtextfiles,andinthosecasesyou might choose one of the other available responseType settings such as text ordocument.Forsoundfiles,arraybufferisused.Thisisageneralcontainerforbinarydatathatisusefulforaudiofiles.vargetSound=newXMLHttpRequest();getSound.open("get","sounds/snare.mp3",true);getSound.responseType="arraybuffer";
Thenextlineofcodebeginswithanonloadfunctionthatisinvokedafterthedata(theaudiofile)hascompletedloading.Withintheonloadfunction,decodingoftheaudiodatatakesplacethat makes it usable by the Web Audio API. You do this with a method calleddecodeAudioData that takes two arguments. The first argument is a property calledresponsethatrepresentstheloaded(andundecoded)audiodata.
getSound.onload=function(){audioContext.decodeAudioData(getSound.response,function(buffer){audioBuffer=buffer;});};
Thesecondargumenttotheonloadfunctionisacallbackfunctionthatallowsyoutocapturetheresultofthedecodedaudiodataanddosomethingwithit.Tocapturethedecodedfile,youmustpassitasanargumentofthecallbackfunction.Inthiscase,thenamegivenforthisdecodedinformationisbuffer.Tomakebufferaccessibletotherestoftheprogram,youcanassignittoaglobalvariable.varaudioContext=newAudioContext();varaudioBuffer;vargetSound=newXMLHttpRequest();getSound.open("get","snare.mp3",true);getSound.responseType="arraybuffer";getSound.onload=function(){audioContext.decodeAudioData(getSound.response,function(buffer){audioBuffer=buffer;//storedasglobalvariable});};
Thelastlineisthesendmethod.ThismethodinitiatestheXMLHttpRequest.getSound.send();
Nowthattheaudiofileisloadedintoabuffer,theplaybackfunctioncontainstherequiredcodetoconnectittothenodegraphandeventuallyplayitback.ThefirstlineassignsamethodcalledcreateBufferSource to avariable.Thismethod isused to create abuffer sourcenodethatisusedforaudiobuffers.Inotherwords,itislikecreateOscillator,butinsteadofbeingusedtocreateoscillators,itisusedtocreateanodethatcanplaybackthecontentsofanaudiobuffer.Toinjecttheaudiobufferintothenodegraph,youneedtoassignittoapropertyofthebuffersourcenodenamedbuffer.functionplayback(){
varplaySound=audioContext.createBufferSource();playSound.buffer=audioBuffer;playSound.connect(audioContext.destination);playSound.start(audioContext.currentTime);}
YoucannowconnectthebuffertotheaudioConext.destinationandsetthestarttime.
functionplayback(){
varplaySound=audioContext.createBufferSource();playSound.buffer=audioBuffer;playSound.connect(audioContext.destination);playSound.start(audioContext.currentTime);}
Thelastlineofcodeisaneventlistenerthatletsyouplaybackthefilewhenthewindowisclicked.
window.addEventListener("mousedown",playback);
Ifyouclickonthepage,youshouldheartheaudiofileplay.
ProcessingtheAudioBufferwiththeNodeGraphWhentheaudiobufferisfedintothenodegraph,youcanprocessitwithitsbuilt-ineffects.Inthefollowingcode,thenodegraphconnectionhasbeenmodifiedtoincludeapropertyoftheaudiobuffer namedplaybackRate. This changes the playback speed of the sound.To double thespeed,setthevalueto2;toplaythesoundbackathalfspeed,setthevalueto0.5.functionplayback(){varplaySound=audioContext.createBufferSource();playSound.buffer=audioBuffer;playSound.playbackRate.value=0.5;playSound.connect(audioContext.destination);playSound.start(audioContext.currentTime);}
SummaryEach time you want to import an audio file into your program, you must initiateXMLHttpRequestwith all themethod and property settings shown in this chapter.You canimagine that duplicating this code repeatedly for each file is unfeasible for a large-scaleapplication.Byabstractingawaythiscomplexity,youcanprogramasolutiontothisproblemthatletsyouimportmultipleaudiofileswithonlyafewlinesofcode.Inthenexttwochapters,youwilllearnhowtodothiswhilelearningabouttwonewobjectcreationmethodologies:factoriesandconstructors.
12FactoriesandConstructors
In the previous chapter, you learned how to import audio files. You also learned that loadingmultiple filescan requirea tremendousamountofcodeduplication.Because repeatingcode issomethingthatshouldbeavoided, it isagoodidea toabstractyouraudiofile loadingprogramintoalibrarythatimportsalltherequiredfileswithaminimalamountofcodeduplication.Inthischapter, youwill learn twonewobject creationpatterns to helpyoudo this.The first pattern,called factory, is used to create your audio loader library. The second pattern, calledconstructor,isintroducedprimarilybecauseofitsprevalenceintheJavaScriptworld,makingitanimportantpatterntofamiliarizeyourselfwith.Factoriesandconstructorsarealmostidentical.Thedifference lies solely inminor implementationdetailsandsyntax. Inotherwords,anythingyou can dowith one of these patterns you can dowith the other.Your choice ofwhich to usecomesdowntopersonalchoice.
Inthenextchapter,youwillputwhatyoulearnheretoworkandbuildyouraudiofileloadinglibrary.
JavaScriptandtheConceptofClassProgramming languages that are organized around objects that interact with one another areusually referred to as object oriented. JavaScript is considered an object-oriented language,although it differs from traditional object-oriented languages in one importantway: JavaScriptlackswhatarecalledclasses.
WhatAreClasses?Withmost object-oriented programming languages, to create an object youmust first create aclass,whichisakindofblueprintthatyourobjectisderivedfrom.Forexample,imagineaclassforamixingconsolethatcontainsanumberofaudiochannels.Whenyoucreateanobjectfromthisclass,youhavetheoptiontodeterminethechannelcountonthefly.Inthisregard,theclass
actsasakindofscaffoldingforthecreationofobjectswhileofferingadegreeofflexibilityforindividualobjectcustomization.
ThebeautyofJavaScriptisthatwhenyoucreateobjectsdirectly,youdonotneedclasses.Infact,classesdon’texist inJavaScript.However, ifyouwant toprograminaclass-basedstyle,youcandosoeasilywitheitheroftwoavailableobjectcreationpatterns:factoryandconstructor.
TheFactoryPatternFactoryisafancytermfordescribingafunctionthatreturnsanobject.functionmakeObj(){varobj={};returnobj;}varnewObj=makeObj();
Youcanusefactoriestosetpropertiesandmethodsontheobjectstheyreturn.Inthefollowingexample,thefactorymakeRecord isusedtocreateobjects thatrepresentmusicalbums.Withfactories,propertyvaluesareassigned to the returnedobject throughfunctionarguments. In thefollowing example, the object’s property values represent information about each record,includingtitle,artist,andyear.functionmakeRecord(title,artist,year){varrecord={};record.title=title;record.artist=artist;record.year=year;
returnrecord;}varweAreHardcore=makeRecord("WeAreHardcore","ThePsychoElectros",2016);
console.log(weAreHardcore.title);//"WeAreHardcore"console.log(weAreHardcore.artist);//"ThePsychoElectros"console.log(weAreHardcore.year);//2016
Ifyouwanttocreatedefaultvaluesforproperties,youcanassignthemlikethis:functionmakeRecord(title,artist,year){varrecord={};record.title=title;record.artist=artist;record.year=year;record.fullAlbum=true;
returnrecord;}
varweAreHardcore=makeRecord("WeAreHardcore","ThePsychoElectros",2016);console.log(weAreHardcore.fullAlbum);//true
Youcanalsoincludemethodsinyourfactories.functionmakeRecord(title,artist,year){varrecord={};record.title=title;record.artist=artist;record.year=year;record.summary=function(){return"Title:"+record.title+".Artist:"+record.artist+".Year:"+record.year;};
returnrecord;}varweAreHardcore=makeRecord("WeAreHardcore","ThePsychoElectros",2016);
console.log(weAreHardcore.summary());/*Title:WeAreHardcore.Artist:ThePsychoElectros.Year:2016*/
DynamicObjectExtensionObjectscreatedwithfactories,likeallobjects,canbeextendedtoincludeadditionalpropertiesandmethods.The followingexamplecreatesanewpropertynamedleadSinger andanewmethod named getAllProperties. getAllProperties loops through the objectpropertiesandlogsthosethatarenotfunctionstotheconsole.functionmakeRecord(title,artist,year){varrecord={};record.title=title;record.artist=artist;record.year=year;record.summary=function(){return"Title:"+record.title+".Artist:"+record.artist+".Year:"+record.year;};returnrecord;}
varweAreHardcore=makeRecord("WeAreHardcore","ThePsychoElectros",2016);weAreHardcore.leadSinger="FredTheButcher";weAreHardcore.getAllProperties=function(){for(varpropinweAreHardcore){if(typeofweAreHardcore[prop]!="function"){//_______________Loopignoresmethods!console.log(prop+":"+weAreHardcore[prop]);//____________Onlyloopsthroughproperties}}};
weAreHardcore.getAllProperties();
/*_____________________RESULT
title:WeAreHardcoreartist:ThePsychoElectrosyear:2016leadSinger:FredTheButcher
___________________________*/
PrivateDataSometimesyouwanttocreatedatathatisaccessibletoyourobjectsbutiseitherinaccessibletotheoutsidescopeorcannotbechanged.Todothis,youcanmakedataprivatebyassigningittoavariableinsidethefactory.Inthefollowingexample,avariablenamedid storessomeprivateinformation.functionmakeRecord(id){varid=id;//Privatedataconsole.log(id+"isprivatedata");varrecord={};returnrecord;}varmyRecord=makeRecord("2323415432");console.log(myRecord.id);/*undefined.Thisisapropertyoftheobject,nottheprivatedata!*/
GettersandSettersPrivatedatacanberetrievedbycreatingamethodinsidethefactorythatisdesignedtoreturnit.Amethodusedtoretrieveprivateinformationiscalledagetter.functionmakeRecord(id){varid=id;varrecord={};record.getId=function(){//getterreturnid;};returnrecord;}
varmyRecord=makeRecord("1121210937");
myRecord.getId();//1121210937
Conversely,methodsthatareusedtomodifyprivatedataarecalledsetters.Inthefollowingcode,asetteriscreatedthatallowsyoutochangethevalueofidwhilerestrictingtheinputtoaten-digitstring.functionmakeRecord(id){varid=id;varrecord={};record.getId=function(){
returnid;};record.setId=function(newId){if(typeofnewId==="string"&&newId.length===10){id=newId;}else{throw("idmustbeaten-digitstring");}
};returnrecord;
}varmyRecord=makeRecord("9876543210");myRecord.getId();//9876543210myRecord.setId("1000000001");myRecord.getId();//1000000001
ProgrammingwithfactoriesisacommonpatterninJavaScriptandonethatyoushouldbesureto familiarize yourselfwith. Factories give you a simple syntax for abstracting complex code,whileofferingyoutheprivacyoffunctionscopecoupledwiththeflexibilityofobjectextension.
ConstructorsandthenewKeywordAnotherpattern forobject creation is called theconstructor.Like a factory, a constructor is afunction that returns an object. The following code shows an implementation of themakeRecordfactoryusingaconstructor.functionRecord(title,artist,year){this.title=title;this.artist=artist;this.year=year;}varweAreHardcore=newRecord("WeAreHardcore","ThePsychoElectros",2016);
console.log(weAreHardcore.title);//WeAreHardcoreconsole.log(weAreHardcore.artist);//ThePsychoElectrosconsole.log(weAreHardcore.year);//2016
Asyoucansee,therearesomedifferencesbetweenfactoriesandconstructors.Thefirstisthenamingconventionforfunctions.Withconstructors,itisconsideredgoodpracticetonamethemwith a capitalized noun. This convention exists solely to help distinguish constructors fromnonconstructorsanddoesnot throwanerror if it isnotused.The lackofanexplicitlycreatedobjectisthenextdifference.Withconstructors,insteadofimmediatelycreatinganobjectinyourfunctiondeclaration,beginbywritingyourpropertiesusingthethiskeyword.Inaconstructor,thispointstotheobjectthatiscreatedfromit.Thesepropertiesareassignedvaluesthroughtheconstructor function arguments or, if you want to create default values, you can assign themdirectlytotheproperty.functionRecord(title,artist,year){this.title=title;
this.artist=artist;this.year=year;this.fullAlbum=true;//defaultvalue}
varweAreHardcore=newRecord("WeAreHardcore","ThePsychoElectros",2016);
console.log(weAreHardcore.fullAlbum);//true
Youinvokeaconstructorusingthenewkeyword.Thisisthecommandthattellstheinterpreterthatyouareusingthefunctionasaconstructor.Inresponse,theinterpretercreatesandreturnsanobject. In the previous example, the return value is assigned to the variable namedweAreHardcore.
AddingMethodstoConstructorsIfyouwanttoaddmethodstoconstructors,thesyntaxlookslikethis:functionRecord(title,artist,year){this.title=title;this.artist=artist;this.year=year;}Record.prototype.summary=function(){return"Title:"+this.title+".Artist:"+this.artist+".Year:"+this.year;};
varweAreHardcore=newRecord("WeAreHardcore","ThePsychoElectros",2016);weAreHardcore.summary();/*Title:WeAreHardcore.Artist:ThePsychoElectros.Year:2016*/
Admittedly,thissyntaxisabitoddlooking.So,toclarifywhatishappening,let’slookattwoconceptsinterwovenwithconstructors:theprototypeobjectandtheprototypeproperty.
ThePrototypeObjectandthePrototypePropertyEverytimeyoucreateafunctioninJavaScript,ahiddenobjectgetscreatedinthebackgroundthatistiedtothefunctionthatcreatedit.Thisobjectisnotvisibleoraccessibleanddoesabsolutelynothing unless you decide to use your function as a constructor. If you use your function as aconstructor, this otherwise dormant object becomes accessible through a property calledprototypeandiscalledtheprototypeobject.
When you attachmethods to constructors, you are expected to attach them to the prototypeproperty,whichinturnsattachesthemtothehiddenprototypeobject.Anyobjectsyoucreatewithyourconstructorhaveaccesstothesemethods.
Record.prototype.summary=function(){return"Title:"+this.title+".Artist:"+this.artist+".Year:"+this.year;};
Althoughyoucanattachyourmethodswithoutusing theprototypeproperty, thedrawback tothisapproachis thateverytimeyoucreateanewobject,allof themethodsare initialized,andthis requires more memory. This might have been a concern in 1995 when JavaScript wasdesignedandcomputersweremuchslower,butthelargeamountofavailablememoryinmoderncomputersmakesthisissuenegligible.Thisisthereasonfactoriesareaviablealternative.Thesyntaxforaddingmethodswithoutusingtheprototypeobjectlookslikethis:functionRecord(title,artist,year){this.title=title;this.artist=artist;this.year=year;this.summary=function(){return"Title:"+this.title+".Artist:"+this.artist+".Year:"+this.year;};}
varweAreHardcore=newRecord("WeAreHardcore","ThePsychoElectros",2016);weAreHardcore.summary();/*Title:WeAreHardcore.Artist:ThePsychoElectros.Year:2016*/
You can use getters and setters, as you do with factories, to work with private data inconstructors.Thefollowingexamplecontainsaprivatevariablenamedidandusesagetter toretrieveit,aswellasasetterthatallowsittobechangedtoaten-digitstring.Notethatthegetterandsetterarenotimplementedontheprototypeproperty,becauseiftheywere,theprivatedatawouldnotbeavailabletothem.functionRecord(id){varid=id;this.getId=function(){returnid;};
this.setId=function(newId){if(typeofnewId==="string"&&newId.length===10){id=newId;}else{throw("idmustbeatendigitstring");}
};}varmyRecord=newRecord("9876543210");myRecord.getId();//9876543210
myRecord.setId("0123456789");myRecord.getId();//0123456789
WhyDoConstructorsExistIfYouCanDotheSameThingwithFactories?
At the timewhenJavaScriptwasdeveloped in1995,oneof themostpopular languages in theworldwasJava.OutofadesiretoappeaseJavadevelopersandlurethemintousingJavaScript,thelanguagewasdesignedtomirrorJava’ssyntax.Partofthiseffortincludedaddingconstructorsto the language thatweredesigned to look like Javaclasses.Thishappened irrespectiveof thefactthatbehindthescenesJavaScriptisnotaclass-basedlanguage.
SummaryIn this chapter, you learned how to create JavaScript pseudoclasses using factories andconstructors.Inthenextchapter,youwillcreateasimplifiedaudiofileloaderlibraryusingthefactorypattern.
13AbstractingtheFileLoader
Now that you are familiar with factories and constructors from the previous chapter, you canabstracttheaudiobufferloaderyoucreatedinChapter11intoalibrarythatloadsmultiplesoundfilesusinglesscode.Youdothisusingthefactorypattern.
ThinkingaboutCodeAbstractionOrganizingyourcodeintoabstractionscanbeadauntingtask.However,therearetwostepsyoucanfollowtosimplifytheprocess.Thefirststepistodeterminewhetheryouneedanabstractioninthefirstplace.Ifyouarerepeatedlytypingoutalargeamountofcodeforthesametask,thenthe answer is probably yes. The second step, if you decide you need an abstraction, is todeterminewhattypeofinterfaceworksforyourabstraction.YouwereexposedtooneexampleofapopularinterfaceinChapter11,whereyouworkedwiththeJQuerylibrary.JQuery’sinterfaceallowsyoutotreatHTMLelementsasobjectsthatyoucanattachmethodsto.Thisisanexcellentchoiceforaninterface,butsometimesasimplefunctioninvocationthatreturnsastringornumberworks justaswell.Ultimately, itdependsonyourobjectiveand thenatureof thecodeyouareabstracting.
Onewaytohelpyoudecideonthebestapproachistoworkbackwardandwriteouthowyouwould like the interface to look and function prior to implementing it. The interface for theaudiofileabstractionyoucreateinthischapterlookslikethefollowingexample:varsound=audioBatchLoader({kick:"kick.mp3",snare:"snare.mp3",hihat:"hihat.mp3",shaker:"shaker.mp3"});sound.snare.play();//Play
Withthisapproach,afactoryfunctiontakesanobjectasanargument.Theobjectyouinputintothefactorycontainsalistofpropertynames,eachofwhichisassignedadirectoryofanaudiofileintheformofastring.Thebeautyofthisapproachisitsclarityandextensibility.Theinterface
shown insound.snare.play() attempts to read, somewhat like English, from the list ofsoundfilestoplay.Evenifyouhaveneverseenthiscodebefore,youcanunderstandwhatitisdoing:selectingasoundnamedsnareandplayingit.Decouplingtheobjectthatcontainsmanyaudiofilesfromtheinvokingfunctionmakesthecodeeasiertoread,asshowninthefollowingexample:varaudioFiles={kick:"kick.mp3",snare:"snare.mp3",hihat:"hihat.mp3",shaker:"shaker.mp3"//______hundredsofaudiofilescouldbelistedhere.........};
varsound=audioBatchLoader(audioFiles);sound.snare.play();//Play
Iftheuserofyourabstractiondecidestheywanttoextendittodonewthings,withouthavingtomodifythesourcecodeintheoriginalfunction,theyhavesomeflexibility.Soforexample,iftheywantedtoextendthereturnedobjecttoplaymultipleaudiobuffers,theycoulddothis:sound.playSnareAndShaker=function(){sound.snare.play();sound.shaker.play();};
sound.playSnareAndShaker();//playstwosoundswithonelineofcode
CreatingtheAbstractionThe following code is the finished abstraction. The remainder of this chapter is dedicated tobuildingupthisexamplelinebylineandexplaininghowitworks.CreateanewtemplateprojectandsavethefollowingcodeintheJavaScriptfolderinafilenamedaudiolib.js."usestrict";
varaudioContext=newAudioContext();functionaudioFileLoader(fileDirectory){varsoundObj={};varplaySound=undefined;vargetSound=newXMLHttpRequest();soundObj.fileDirectory=fileDirectory;getSound.open("GET",soundObj.fileDirectory,true);getSound.responseType="arraybuffer";getSound.onload=function(){audioContext.decodeAudioData(getSound.response,function(buffer){soundObj.soundToPlay=buffer;
});};
getSound.send();
soundObj.play=function(time){playSound=audioContext.createBufferSource();playSound.buffer=soundObj.soundToPlay;playSound.connect(audioContext.destination);playSound.start(audioContext.currentTime+time||audioContext.currentTime);};
soundObj.stop=function(time){playSound.stop(audioContext.currentTime+time||audioContext.currentTime);};returnsoundObj;}
functionaudioBatchLoader(obj){
for(varpropinobj){obj[prop]=audioFileLoader(obj[prop]);
}
returnobj;
}
varsound=audioBatchLoader({
kick:"sounds/kick.mp3",snare:"sounds/snare.mp3",hihat:"sounds/hihat.mp3",shaker:"sounds/shaker.mp3"
});
window.addEventListener("mousedown",function(){sound.snare.play();});
Younowneedtoreferencethefileinyourindex.htmlfile.
<head><metacharset="UTF-8"><title></title><scriptsrc="js/audiolib.js"></script><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"></head>
Createafoldernamedsounds.Thisisthedirectoryusedtoholdyouraudiofiles.
WalkingthroughtheCodeThefunctionnamedaudioFileLoadercreatesandreturnsanobjectnamedsoundObj.functionaudioFileLoader(){varsoundObj={};returnsoundObj;};
To specify a directory for the file to be used, a parameter is assigned to a property ofsoundObjnamedfileDirectory.
functionaudioFileLoader(fileDirectory){varsoundObj={};soundObj.fileDirectory=fileDirectory;returnsoundObj;};
Youcannowcreate theXMLHttpRequest object and set all the required properties andmethods.YoucanalsoimplementthedecodeAudioDatamethodtomakethebufferusablebytheWebAudioAPI.Theselinesofcodeshouldalreadybefamiliartoyoubecausetheyarethesame buffer loading and decoding tools you learned about in Chapter 11, with one smalldifference.InChapter11thedecodedbufferwasassignedtoavariablenamedaudioBuffer.In this implementation, the decoded buffer is assigned to a property of soundObj namedsoundToPlay.functionaudioFileLoader(fileDirectory){varsoundObj={};vargetSound=newXMLHttpRequest();soundObj.fileDirectory=fileDirectory;getSound.open("GET",soundObj.fileDirectory,true);getSound.responseType="arraybuffer";getSound.onload=function(){audioContext.decodeAudioData(getSound.response,function(buffer){soundObj.soundToPlay=buffer;//Propertyassignedbuffer
});
};
getSound.send();returnsoundObj;}
Youcannowcreateaplaybackmethod that is anextensionofsoundObj toplayback thebuffers.functionaudioFileLoader(fileDirectory){
varsoundObj={};soundObj.fileDirectory=fileDirectory;vargetSound=newXMLHttpRequest();getSound.open("GET",soundObj.fileDirectory,true);getSound.responseType="arraybuffer";getSound.onload=function(){audioContext.decodeAudioData(getSound.response,function(buffer){soundObj.soundToPlay=buffer;
});};
getSound.send();
soundObj.play=function(time){
varplaySound=audioContext.createBufferSource();playSound.buffer=soundObj.soundToPlay;playSound.connect(audioContext.destination);playSound.start(audioContext.currentTime+time||audioContext.currentTime);};
returnsoundObj;
}
Thetime argument of theplay function determines the number of seconds youwant theaudiofiletoplayintothefuture.Thelogicalexpression(audioContext.currentTime+time||audioContext.currentTime)isusedtodeterminewhetherthetimeargumentis empty and, if it is, then the start method does not add additional seconds to the value ofaudioContext.currentTime.Whennoargumentsareset,thesoundplaysimmediately.soundObj.play=function(time){
varplaySound=audioContext.createBufferSource();playSound.buffer=soundObj.soundToPlay;playSound.connect(audioContext.destination);playSound.start(audioContext.currentTime+time||audioContext.currentTime);};
Thestopmethodletsusersdeterminewhenasoundwillstopplayback.soundObj.stop=function(time){playSound.stop(audioContext.currentTime+time||
audioContext.currentTime);}
Youcannowloadthefilesandplaythem.varsound=audioFileLoader("sounds/snare.mp3");window.addEventListener(“mousedown”,function(){sound.play();//playsat"currenttime"becausenoargumentsaresetsound.play(2);//plays2secondsintothefuture});
Thiscodeworks,butitrevealsanewpotentialproblem.Ifyouwanttoloadmultiplefiles,youhavetotypeoutanaudioFileLoaderinvocationforeachone,likethis:varkick=audioFileLoader("sounds/kick.mp3");varsnare=audioFileLoader("sounds/snare.mp3");varhihat=audioFileLoader("sounds/hihat.mp3");varshaker=audioFileLoader("sounds/shaker.mp3");
OnewaytomitigatethisadditionalrepetitionistocreateahelperfunctionthatloopsthroughanobjectthatcontainsacollectionofaudiofiledirectoriesandinvoketheaudioFileloaderoneachfile.Youcanthenreturntheobject.Thiswillalloweachsoundtobeaccessiblevia itspropertyname.Thefollowingcodedemonstratesthis:functionaudioBatchLoader(obj){for(varpropinobj){obj[prop]=audioFileLoader(obj[prop]);}returnobj;}
varsound=audioBatchLoader({kick:"sounds/kick.mp3",snare:"sounds/snare.mp3",hihat:"sounds/hihat.mp3",shaker:"sounds/shaker.mp3"});
Eachfileisnowaccessibleusingthefollowingsyntax:sound.kick.play();sound.snare.play();sound.hihat.play();sound.shaker.play();
Younowhaveaworkinglibrarytoloadmultipleaudiofiles.Thefollowingcodesetsaneventlisteneronthewindow.Ifyouclickit,youwillheartheloadedsoundplay.varsound=audioBatchLoader({
kick:"sounds/kick.mp3",snare:"sounds/snare.mp3",hihat:"sounds/hihat.mp3",shaker:"sounds/shaker.mp3"
});
window.addEventListener("mousedown",function(){
sound.snare.play();
});
SummaryInthischapter,youlearnedthebasicsofhowtothinkaboutabstraction,whilecreatinganewtoolforloadingandplayingbackmultipleaudiofiles.Inthenextfewchapters,youwilllearnhowtomanipulateaudioviathenodegraphusingvariouseffects.
14TheNodeGraphandWorkingwithEffects
Uptothispoint,thetopicofthenodegraphhasonlybeenpartiallydescribedandhasbeenusedmostlyasatooltoexplainrelatedconcepts.Inthischapter,youwilllearnhowtoworkwiththenodegraphtodevelopcustomsignalchainsforcomplexaudioapplications.TheWebAudioAPIincludesmanybuilt-inobjectsthatletyoumanipulateaudioincreativeways.Youalsolearnhowtoincludetheseobjectsinyourapplicationsandusethemtocreatecustomizedeffects.
HowtoThinkAbouttheNodeGraphIna real-world recordingstudio,you routeaudio signalsbyconnectingmicrophonesandothersound sources to a soundmixer. The soundmixer is configuredwith its own routing scheme,whichallowsaccesstoequalizers,dynamicsprocessors,andothereffects.TheWebAudioAPInodegraphisdesignedtomirrorthecharacteristicsofareal-worldsoundmixer.Thisisdonebyconnectinginputsourcessuchasoscillatorsandaudiobufferstootherobjectsthatmanipulatethesoniccharacteristicsoftheseinputsourcesinsomeway.Thevariousobjects(includingtheinputsources)thatmakeupthesignalchainsarecallednodesandareconnectedtooneanotherusingamethod namedconnect(). You can think ofconnect() as a virtual audio cable used tochaintheoutputofonenodetotheinputofanothernode.ThefinalendpointconnectionforanyWebAudioapplication isalwaysgoing tobe theaudioContext.destination.You canthink of the audioContext.destination as the speakers of your application. Thiscollectionofconnectionsiswhatisreferredtoasthenodegraph,showninthefigurebelow.
GainNodesInareal-worldrecordingstudio,youtypicallyuseasoundmixerwithmultiplechannelstripsandaroutingmatrixtosplitandcombineaudiosignals.WiththeWebAudioAPInodegraph,youusegainnodes to split and combine inputsources.Gain nodes allow independent volume controloverinputsourcesandactasvirtualmixingchannels.
Thefollowingcodeisanexampleofcreatingtwooscillatorsandconnectingeachonetoanindependent gain node for individual volume control. These are summed to a third gain node,whichisconnectedtotheaudioContext.destination.//________________________________BEGINcreatesawtoothoscillatorvaroscSaw=audioContext.createOscillator();oscSaw.type="sawtooth";oscSaw.frequency.value=118;oscSaw.start(audioContext.currentTime);//________________________________ENDcreatesawtoothoscillator
/*________________________________BEGINcreategainnodeandconnectsawtoothoscillator*/
vargainSaw=audioContext.createGain();gainSaw.gain.value=0.6;//setvolumeoscSaw.connect(gainSaw);
/*________________________________ENDcreategainnodeandconnectsawtoothoscillator*//*________________________________BEGINcreatetrianglewaveoscillator*/varoscTri=audioContext.createOscillator();oscTri.type="triangle";oscTri.frequency.value=120;oscTri.start(audioContext.currentTime);/*________________________________ENDcreatetrianglewaveoscillator*/
/*________________________________BEGINcreategainnodeandconnecttrianglewaveoscillator*/
vargainTri=audioContext.createGain();gainTri.gain.value=3;//setvolumeoscTri.connect(gainTri);
/*________________________________ENDcreategainnodeandconnect
trianglewaveoscillator*/
//____SUMBothOscillators___vargainOscSum=audioContext.createGain();gainOscSum.gain.value=1;gainTri.connect(gainOscSum);gainSaw.connect(gainOscSum);//____ConnecttotheaudioContext.destinationgainOscSum.connect(audioContext.destination);
ThePlacementofNodesIsUptoYouItisimportanttorealizethatduetotheflexiblenatureofthenodegraph,youcanplaceyourinputsourcesandothernodesatanypartofthechain.Imagineyouhavetengainnodesallconnectedinseries,andyouwanttoinjectanoscillatorintothesixthone.Thisisperfectlyfinebecausetheoscillatorisunaffectedbythefirstfivegainnodesinthechainbybeingfunneledthroughthelastfive gain nodes prior to reaching the audioContext.destination. This is not just afeatureofgainnodesbut thenatureof thenodegraph.Youcanplaceany inputsource,oranyothernode,anywhereyouwantinthenodegraphsignalchain.Theorderinwhichyouconnectobjectsisdependentontheresultyouwant.
WhatEffectsAreAvailable?Thefollowingchartcontainssomeofthenodesthatarecharacteristicoftheeffectprocessorsyouseeinreal-worldrecordingstudios.Theseeffectsarecalledmodificationnodes,butforclaritytheyarereferredtoaseffectsnodesinthisbook.Thespecificsofeffectsnodesareexploredinlater chapters. The focus of this chapter is to give you a general understanding of how theseeffectsnodescanbeincorporatedintothenodegraph.
HowtoDeterminetheNodesYouNeedtoCreatetheEffectYouWant
Ifyouhavean ideaforaneffectyouwant to incorporate intoyourapplication,youcanfollowthesestepstohelpdeterminethetoolsyouneedtocreateit:
1. Determine the specific type of effect you want (chorus, tremolo, hall reverberation,multiband,EQ,etc.).
2.Determinethenatureoftheeffect.Inotherwords,iftheeffectyouwantisachorus,thenthenatureoftheeffectisanaudibledelay.Iftheeffectyouwantisamultibandequalizer,thenthenatureoftheeffectisaudiofiltering.
3. Research theWebAudioAPI specification to find a node that you can use to create theeffect.Manytimes,creatingtheexacteffectyouwantrequirescombiningdifferentnodesorcombiningsimilarnodeswithslightlymodifiedparameters.
4.InvoketherespectivemethodoftheAudioContexttocreatethenode(ornodesifyouareusing more than one). This is a method that starts with the word create, such ascreateGain()orcreateBiquadFilter().
5.Connecttheobject(orobjectsifyouareusingmorethanone)tothenodegraphinthepartofthesignalchainthatyouwant.
6.Modifythebuilt-inpropertiesandmethodsoftheobject(s)tomanipulatetheinputsource(s)inthemanneryouwant.
AReal-WorldExampleAssumeyouwanttoapplyalow-pass(alsocalledlowpass)filtertoanoscillator.(Alow-passfilterisafilterthatonlyallowssignalsbelowacertainfrequencytopass.)Todothis,youfirstresearch theWebAudioAPIdocumentation toseewhether this typeof filter issupported.Youcan search the specification directly at: https://www.w3.org/TR/webaudio. An alternativereference(andonethatisabitmorereadable)istheMozillaDeveloperNetworkdocumentationat:https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API.
Whenresearching,doasearchfor“filters”or“lowpass.”Intheresults,youwilldiscoverthatthereisaspecializednodecalledabiquadfilter that isdedicatedtoaudiofiltering.Thisnodeincludesapropertynamedtypethatyoucansettolowpass.Asthevalueimplies,thisfiltertypeisusedtoapplyalow-passfiltertoaninputsource.Toapplythistoyourapplication,youfirstinvokethecreateBiquadFilter()method,whichreturnsanobjectthatyoustoreinavariable.You thenconnect an input source, suchas anoscillatoror arraybuffer, to thisobjectusingtheconnectmethod.varaudioContext=newAudioContext();
varosc=audioContext.createOscillator();osc.start(audioContext.currentTime);varfilter=audioContext.createBiquadFilter();filter.type="lowpass";osc.connect(filter);filter.connect(audioContext.destination);
The final step is to define any additional properties or methods to customize the effect.Propertiesormethodsthatallowyoutocustomizethebehaviorofnodesarecalledaudioparams(shortforaudioparameters).Inthepreviousexample,type isanaudioparam.Thefollowingcodesetsanotheraudioparamnamedfrequencytothevalue250.Thisdefineswherethelow-passfilterbeginstocutoffinthefrequencyspectrum.varaudioContext=newAudioContext();
varosc=audioContext.createOscillator();osc.start(audioContext.currentTime);varfilter=audioContext.createBiquadFilter();filter.type="lowpass";//audioparamfilter.frequency.value=250;//audioparamosc.connect(filter);filter.connect(audioContext.destination);
SomeEffectsRequireDevelopmentWork
It is important tounderstand that theWebAudioAPI’seffectsnodesarebuildingblocks. Thismeansthatsomeoftheeffectsyouwanttoachievemightrequireadditionaldevelopmentworkonyourpart.Forexample, ifyouwant touseamultiband equalizer, youwon’t find a “multibandequalizernode”intheWebAudioAPIspecification.Instead,youmustbuildyourownmultibandequalizerusingacollectionofBiquadFilternodes.
SummaryInthischapter,youwereformallyintroducedtothenodegraphandhowtocreatecustomsignalchains using input sources and effects nodes. In the next few chapters, youwill build on thisknowledgeandexplorethespecificsofsomeoftheseeffectsnodes.
15TheBiquadFilterNode
One of the most common ways to manipulate sound is by boosting or attenuating a range offrequencies using audio filters. A familiar example of this is the use of audio equalizers tobrightenormuffleasound.TheWebAudioAPIhasanodenamedBiquadFilterthatallowsyoutocreatedifferenttypesofaudiofiltersthatcanbeconnectedtogethertocreatevariousformsofequalizers. In thischapter,youwill learnhowtouse theBiquadFilter node, and in theprocess,youwillcreateaseven-bandgraphicequalizerandasingle-bandparametricequalizer.
UsingtheBiquadFilterNodeTo use the BiquadFilter node, you must first instantiate it using the createBiquadFilterfunctionandstorethereturnedobjectinavariable.varaudioContext=newAudioContext();varfilter=audioContext.createBiquadFilter();
Once you create the object, you can connect an input source to it. The following exampleconnectsanoscillatortotheobject.varaudioContext=newAudioContext();varosc=audioContext.createOscillator();varfilter=audioContext.createBiquadFilter();osc.connect(filter);//connectinputsourcetofilterosc.start(audioContext.currentTime);filter.connect(audioContext.destination);/*connectfiltertoaudioContext.destination*/
FilterTypesBiquadFilter contains a property named type that defines the type of filter the nodebehaveslike.Ifyoudonotexplicitlysetthetypeproperty,itsdefaultvalueislowpass.You
canseethisintheconsole.log()outputinthefollowingcode:
varaudioContext=newAudioContext();varosc=audioContext.createOscillator();varfilter=audioContext.createBiquadFilter();filter.frequency.value=250;console.log(filter.type);//defaultislowpassosc.connect(filter);osc.start(audioContext.currentTime);filter.connect(audioContext.destination);
Toexplicitlysetthetypepropertytolowpass,youwritethefollowingcode:filter.type="lowpass";
In addition to the type property, BiquadFilter has a property namedfrequency.valuethatallowsyoutoassignaparticularfrequencytotheobject.Thevalueisinhertzandisrepresentedbyanumber.Thedefaultvalueis350.filter.frequency.value=1000;//1000Hzor1kHz
ThetypevalueofaBiquadFilternodedetermines if ithas twoadditionalproperties:gain and Q. The gain.value property allows you to boost or attenuatefrequency.value.TheQ.valuepropertyrepresentsthebandwidthofthefrequencyvalue.Bandwidth represents the reach by which neighboring frequencies are affected in relation tochanges made to the gain of the selected frequency. The following images demonstrate thedifference between a narrow bandwidth setting and a wide bandwidth setting of a 1 kHzfrequencyusingapeakingfilter.
The effect Q.value and gain.value have on frequency.value depends on thefilter’s type setting. The following chart lists the available filter types and describes therelationshipbetweentype,frequency,Q,andgainproperties.
CreatinganEqualizerTwo of themost common types of equalizers are parametric and graphic.A graphic equalizerallowsyoutoboostorattenuateaseriesoffixedfrequenciesbutdoesnotincludetheabilitytomodifythebandwidthofthoseselectedfrequencies.Parametricequalizers,onthecontrary,allowyoutoselectaspecificfrequency,boostorattenuateit,andchangethebandwidthrange.YoucanuseBiquadFilternodestodesigneitheroftheseequalizers,andmanyothers.
GraphicEQThefollowingdiagramandcodeshowhowtocreateaseven-bandgraphicequalizer.Youdothisby chaining a series ofBiquadFilter nodes together and setting theirtype properties to
peaking. Keep in mind that the only parameter the user of a graphic equalizer should beallowed to change is the gain of each filter. The input and output source for this example isabstractedusingafunctionnamedmultibandEQ.
varfilter1=audioContext.createBiquadFilter();filter1.type="peaking";/*______Donotletusermodify.ThisisagraphicEQ!*/filter1.gain.value=0;filter1.Q.value=1;/*___________Donotletusermodify.ThisisagraphicEQ!*/filter1.frequency.value=64;/*__Donotletusermodify.ThisisagraphicEQ!*/
varfilter2=audioContext.createBiquadFilter();filter2.type="peaking";/*______Donotletusermodify.ThisisagraphicEQ!*/filter2.gain.value=0;filter2.Q.value=1;/*___________Donotletusermodify.ThisisagraphicEQ!*/filter2.frequency.value=150;/*_Donotletusermodify.ThisisagraphicEQ!*/
varfilter3=audioContext.createBiquadFilter();filter3.type="peaking";/*______Donotletusermodify.ThisisagraphicEQ!*/filter3.gain.value=0;filter3.Q.value=1;/*___________Donotletusermodify.ThisisagraphicEQ!*/filter3.frequency.value=350;/*_Donotletusermodify.ThisisagraphicEQ!*/
varfilter4=audioContext.createBiquadFilter();filter4.type="peaking";/*______Donotletusermodify.ThisisagraphicEQ!*/filter4.gain.value=0;filter4.Q.value=1;/*___________Donotletusermodify.ThisisagraphicEQ!*/filter4.frequency.value=1000;/*Donotletusermodify.ThisisagraphicEQ!*/
varfilter5=audioContext.createBiquadFilter();filter5.type="peaking";/*______Donotletusermodify.ThisisagraphicEQ!*/filter5.gain.value=0;filter5.Q.value=1;/*___________Donotletusermodify.ThisisagraphicEQ!*/filter5.frequency.value=2000;/*Donotletusermodify.ThisisagraphicEQ!*/
varfilter6=audioContext.createBiquadFilter();filter6.type="peaking";/*______Donotletusermodify.ThisisagraphicEQ!*/filter6.gain.value=0;filter6.Q.value=1;/*___________Donotletusermodify.Thisisa
graphicEQ!*/filter6.frequency.value=6000;/*Donotletusermodify.ThisisagraphicEQ!*/
varfilter7=audioContext.createBiquadFilter();filter7.type="peaking";/*______Donotletusermodify.ThisisagraphicEQ!*/filter7.gain.value=0;filter7.Q.value=1;/*___________Donotletusermodify.ThisisagraphicEQ!*/filter7.frequency.value=12000;/*Donotletusermodify.ThisisagraphicEQ!*/
functionmultibandEQ(inputConnection,outputConnection){
inputConnection.connect(filter1);filter1.connect(filter2);filter2.connect(filter3);filter3.connect(filter4);filter4.connect(filter5);filter5.connect(filter6);filter6.connect(filter7);filter7.connect(outputConnection);
}
Thecodefilesforthischapterincludeversionsofboththegraphicandparametricequalizerswithuser interfacecontrols.TheseapplicationsallowyoutotoggletheplaybackofasongandchangeparametersoftheBiquadFilternodesinrealtimebyusingtheinteractivesliders.
ParametricEQ
Youcandesign aparametric equalizer in a similarway to thegraphic equalizer by chaining aseries ofBiquadFilter nodes together and setting their type properties topeaking. Theprimary difference of the parametric equalizer is that the frequency, gain, and bandwidth aremodifiableby theuser.Keep inmind thatwithmultibandparametric equalizers, the filter typemayhavemultipleoptionsavailable.Tokeepthecodesimpleandshort,thefollowingexampleshowshow to create a single-bandparametric equalizerwith type set to thevaluepeaking.TheinputandoutputsourceinthiscodeisabstractedusingafunctionnamedparametricEQ.varparametricEQ1=audioContext.createBiquadFilter();parametricEQ1.type="peaking";parametricEQ1.gain.value=0;//allowtheusertochangethisparametricEQ1.Q.value=1;//allowtheusertochangethisparametricEQ1.frequency.value=1000;functionparametricEQ(inputConnection,outputConnection){inputConnection.connect(parametricEQ1);parametricEQ1.connect(outputConnection);}
SummaryInthischapter,youlearnedabouttheBiquadFilternodeandhowtouseittocreatecustomequalizersandfilterarrangements.Keepinmindthattheexamplesherearekeptsimple,andlikethenodegraphitself,yourfilterarrangementscanbeascomplexasyouwanttomakethem.Inthenextchapter,youwilllearnaboutanothersignalprocessingnode:theconvolvernode.
16TheConvolverNode
Inthischapter,youwilllearnhowtousetheconvolvernode.Theconvolverallowsyoutoapplyreverberation tonodegraph input sourcesby referencingaspecialkindofaudio filecalledanimpulseresponse.
ConvolutionReverbWhenanacousticsoundiscreated,itscharacteristicsareshapedbyitsimmediateenvironment.This isduetosoundwavesbouncingoffandaroundvariousobstacles.Theseobstaclescanbemadeofdifferentmaterialsthataffectthesoundindifferentways.Theresultofsoundemanatingfromasmallroomhasdifferentcharacteristicsthansoundemanatingfromalargeroom.Becausethehumanear canhear thesedifferences,when this information is transmitted to thebrain,weperceive these characteristics as room ambience. Modern advancements in digital audiotechnologyallowus to record theambienceofany real-worldenvironmentandapply it toanydigital audio signal directly. These recorded ambiences are stored as a special file called animpulse response. An impulse response file is made by recording a single sound burst in anenvironment, which could be white noise, a sine wave sweep, or even a balloon pop. Thisrecordingisthenrunthroughaspecialdigitalalgorithmtocreateasinglefilecalledanimpulseresponse.Thisimpulseresponsefileiscombinedorconvolvedwithanotherinputsourcetogivethetargetedsoundthespacialcharacteristicsoftheroomthattheimpulseismodeledfrom.
TheformatofimpulseresponsefilescanbeanyaudiofiletypeincludingWAV,MP3,AIFF,orOGG. However, to use them with the Web Audio API, impulse response files must be in abrowser-compatibleaudioformat.Forthischapter,weuseWAVfilesbecausetheyareofhigherqualitythanMP3files.Andbecauseimpulseresponsefilesaresmall,loadtimeisnotaconcern.
WheretoGetPre-RecordedImpulseResponseFiles
Therearemanyonlineresourceswhereyoucandownloadimpulseresponsefilesforfree,suchas:http://www.openairlib.net/.
UsingImpulseResponseFilesTouseimpulseresponsefiles,youmustfirstloadthem,decodethem,andstoretheminabuffer.varaudioContext=newAudioContext();varimpulseResponseBuffer;vargetSound=newXMLHttpRequest();getSound.open("get","sounds/impulse.js",true);//impulsefilegetSound.responseType="arraybuffer";
getSound.onload=function(){audioContext.decodeAudioData(getSound.response,function(buffer){impulseResponseBuffer=buffer;});};getSound.send();
Afterthefileisstoredinabuffer,thenextstepistowireupthenecessarynodestoapplytheeffecttoaninputsource.Tointegratetheimpulseresponseintothenodegraphconfiguration,youmustfirstcreateaconvolvernodeusingaudioContext.createConvolver()andstorethereturnedobjectinavariable.varconvolver=audioContext.createConvolver();
Youthenassigntheloadedimpulseresponsebuffertothebufferpropertyoftheobject.convolver.buffer=impulseResponseBuffer;
Next, you connect any input sourceyouwant to the convolver node.Here is an exampleofconnectinganoscillator.varosc=audioContext.createOscillator();varconvolver=audioContext.createConvolver();osc.type="sawtooth";convolver.buffer=impulseResponseBuffer;osc.connect(convolver);convolver.connect(audioContext.destination);osc.start(audioContext.currentTime);
The following HTML and JavaScript code combines the impulse file loader, node graphconnections,andJQueryDOMselectorstoallowyoutoplaytheoscillatorbyclickinganHTMLbuttonandholdingit.Thisallowsyoutohearthereverberationeffectmoreexplicitlybecausethereverb tail is audible after removing your finger from the mouse button and stopping theoscillator.
HTML
<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title></title><scripttype="text/javascript"src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.js"></script><scriptsrc="js/app.js"></script></head><body><button>Oscillation</button></body></html>
JavaScript"usestrict";varaudioContext=newAudioContext();varimpulseResponseBuffer;vargetSound=newXMLHttpRequest();getSound.open("get","sounds/impulse.wav",true);getSound.responseType="arraybuffer";
getSound.onload=function(){audioContext.decodeAudioData(getSound.response,function(buffer){impulseResponseBuffer=buffer;});};
getSound.send();
/*___________________________________________BEGINplaybackfunctionality*/
varosc=audioContext.createOscillator();
functionplayback(){varconvolver=audioContext.createConvolver();osc=audioContext.createOscillator();osc.type="sawtooth";convolver.buffer=impulseResponseBuffer;osc.connect(convolver);convolver.connect(audioContext.destination);osc.start(audioContext.currentTime);}
$(function(){
$("button").on("mousedown",function(){playback();});
$("button").on("mouseup",function(){osc.stop();});});
ControllingtheAmountofReverberationIn the previous code example, the amount of reverb applied to the oscillator is fixed at 100percent. Ifyouwant tomake theeffectvariable,whichallowsyou tocontrolhowmuchof theeffectisappliedtotheinputsource,youcandosobysplittingtheinputsourcewithagainnodeand routing one split to the convolver node prior to connecting it to the destination.You thenconnecttheothersplitdirectlytothedestination.Youusegain.valuetoblendtheamountoftheeffectyouwanttohear.
Thefollowingdiagramandnodegraphconfigurationcodedemonstratethesplittingoperation.
vargain=audioContext.createGain();varconvolver=audioContext.createConvolver();
osc=audioContext.createOscillator();osc.type="sawtooth";convolver.buffer=impulseResponseBuffer;osc.connect(convolver);convolver.connect(gain);gain.gain.value=0.2;gain.connect(audioContext.destination);osc.connect(audioContext.destination);osc.start(audioContext.currentTime);
SummaryInthischapter,youlearnedhowtousetheconvolvernodetoapplyanimpulseresponsefiletoaninputsource.Youalsolearnedhowtousegainnodestocontroltheamountoftheeffectyouwanttohear.Inthenextchapter,youwilllearnhowtomodifythepanningofstereoinputsourcesandhowtocreatesophisticatedroutingschemesusingthechannelandmergernodes.
17StereoPanning,ChannelSplitting,andMerging
TheWebAudioAPIincludesastereopannernodethatletsyoupaninputsourcestoanypartofthe stereo field. It also includes nodes that let you splitmultichannel audio files into separatechannels as well as merge multichannel input sources into a specified output channel. In thischapter,youwilllearnhowtousethesenodestomanipulatemultichannelinputsources.
TheStereoPannerNodeTo use the stereo panner node, you first invoke createStereoPanner() and store thereturnedobjectinavariable.varstereoPanner=audioContext.createStereoPanner();
Youcanthenconnectanyinputsourcetothenodeandusepan.valuetosetthelocationinthestereofieldwhereyouwanttoplacethesound.Thepan.valuepropertysettingisanumberbetween1 and−1,where1 represents a100percent pan to the right and−1 represents a 100percent pan to the left. In the following example, anoscillator is connected to a stereopannernodeandisset50percenttotheleft.varoscillator=audioContext.createOscillator();varstereoPanner=audioContext.createStereoPanner();stereoPanner.pan.value=-0.5;oscillator.connect(stereoPanner);stereoPanner.connect(audioContext.destination);oscillator.start(audioContext.currentTime);
ThestereoPanner()usesanequalpoweralgorithmtopaninputsources.Thismeansthatwhenastereoinputsourceispanned,theaudiocontentontheattenuatedsideissummedwiththeaudioontheamplifiedside.
TheChannelSplitterIfyouwanttoisolatetheindividualchannelsofamultichannelinputsourceordonotwantyourstereo input sources to be subjected to an equal power algorithm, you must use the channelsplitter.Thisnodeisolatesanychannelofamultichannelinputsourceforfurtherprocessing.Thisappliestobothstereoandothermultichannelaudioinputsources,suchas5.1surroundfiles.Tocreateachannelsplitter,youinvokethecreateChannelSplitter()methodwithasingleargument and store the returned object in a variable. The argument value is the number ofchannelsoftheaudiosourcematerialthatyouintendtoconnecttothesplitter.Ifnoargumentisspecified,thedefaultis6.Inthefollowingexample,astereofileissplit,sotheargumentissetto2.varsplitter=audioContext.createChannelSplitter(2);
Tousethechannelsplitter,youconnectinputsourcestoitandthenconnectthesplittertoothernodes.When connecting the splitter to a destination node, you specify the channel of the inputsource to connect to in the second argument of theconnect() method. This argument is anumberthatrepresentsthechannelasanindexvalue.Thefollowingchartdisplaystheindexforeachchannelofasix-channelinputsource.
The following code shows the correspondence between the channel index argument and itsrespectivechanneltype.stereoInputSource.connect(splitter);splitter.connect(audioContext.destination,0);/*outputsleftside/channelofstereoinputsource*/splitter.connect(audioContext.destination,1);/*outputsrightside/channelofstereoinputsource*/
Thefollowingcodeshowshowtomodifythegainvalueofindividualleftandrightchannelsof a stereo input source. In otherwords, this configuration is the opposite of an equal powerpanningalgorithm.varsplitter=audioContext.createChannelSplitter(2);varpannerLeft=audioContext.createStereoPanner();varpannerRight=audioContext.createStereoPanner();varleft=audioContext.createGain();varright=audioContext.createGain();sound=audioContext.createBufferSource();sound.loop=true;
sound.buffer=bufferSource;sound.connect(splitter);splitter.connect(left,0);//___connectleftchanneltogainnodesplitter.connect(right,1);//__connectrightchanneltogainnodeleft.gain.value=leftVal;/*_________independentleftchannelcontrol*/right.gain.value=rightVal;/*________independentrightchannelcontrol*/left.connect(pannerLeft);pannerLeft.pan.value=-1;pannerRight.pan.value=1;right.connect(pannerRight);pannerLeft.connect(audioContext.destination);pannerRight.connect(audioContext.destination);sound.start(audioContext.currentTime+time||audioContext.currentTime);
TheChannelMergerIfyouwanttocombinemultiplemonoinputsourcesandroutethemtoaspecificchannelinthestereo (or multichannel) spectrum, you use a channel merger. The function invocation for thechannel merger node takes one argument that determines how many input channels the objectaccepts.Ifnoargumentisgiven,thedefaultis6.varmerger=audioContext.createChannelMerger();
When connecting an input source to a channelmerger, youmust specify the output channelusingthethirdargumentoftheconnectmethod.inputSource.connect(merger,0,1);/*outputsallchannelsofinputSourcetorightchannel*/
MergingAllChannelsofaMultichannelFileintoaSingleMonoChannel
Tocombineamultichannel file into a singlemono output,which is placed at the center of thestereospectrum,yousetthechannelmergerinvocationargumentto1,andthenconnecttheinputsourcetothechannelmerger.varmultiChannelInputSource=audioContext.createBufferSource();varmerger=audioContext.createChannelMerger(1);/*Setnumberofchannels*/stereoInputSource.buffer=audioBuffer;stereoInputSource.connect(merger);merger.connect(audioContext.destination);
UsingtheMergerandSplitterNodesTogetherThemergerandsplitternodescanbeusedinconjunctionwithoneanothertoroutespecificinputchannelstospecificoutputchannels.Thefollowingcodetakestheleftandrightsidesofastereoinputsourceandswapsthem.stereoInputSource.connect(splitter);splitter.connect(merger,0,1);//inputleftandoutputrightsplitter.connect(merger,1,0);//inputrightandoutputleftmerger.connect(audioContext.destination);
Ifyouconnectanaudioinputsource,suchasanaudiobuffersourcenode,directlytoachannelmergernode, there isnoreasontoset thesecondargumentof theconnectmethod toavalueotherthan0.Thisisbecausethemergernodehasasingleoutput.audioBufferSource.connect(merger,0,1);
If the inputof a channelmerger is a channel splitter, the secondargumentof theconnectmethodisthechanneloftheinputsourcesenttothemerger.varchannelSplitter=audioContext.createChannelSplitter();varchannelMerger=audioContext.createChannelMerger();varsound=audioContext.createBufferSource();sound.buffer=audioBuffer;sound.connect(channelSplitter);channelSplitter.connect(channelMerger,0,0);/*TheleftchannelofplaySoundisconnectedtothechannelmerger*/channelMerger.connect(audioContext.destination);
SummaryInthischapter,youlearnedhowtoapplystereopanningtoaudioinputsources.Youalsolearnedhow toworkwith the channel splitter and channelmerger nodes. In the next chapter, youwillexplorehowtocreatedelayeffectsusingthedelaynode.
18TheDelayNode
Intheworldofcreativeaudio,delaysareacommonmethodusedtocreatetime-basedeffects.Inthischapter,youwilllearnhowtousethedelaynodetocreatethemostcommondelayeffects:echo,slapback,andping-pong.
TheDelayNodeThe delay node is used to adjust the time between when an input source plays and when itbecomes audible. The following example connects an audio buffer to a delay node. ThedelayTime.valuepropertydeterminesthedelaytimeinseconds.varsound=audioContext.createBufferSource();vardelay=audioContext.createDelay();delay.delayTime.value=1;//Onesecondsound.buffer=audioBuffer;sound.connect(delay);delay.connect(audioContext.destination);sound.start(audioContext.currentTime);
Ifyoulistentotheresultofthepreviousexample,youwillnoticethatitdoesnotprovidetherepetitiveechodelayeffectthatistypicalofaneffectsprocessor.Thisisbecausetheonlythingthe delay node does is pause the audio from playing for a set amount of time. If you want arepetitiveechoeffect,youmustcreateit.
CreatingEchoEffectsTocreateanechoeffect,youconfigureanodegraphschemethatsetsthedelayedsignaltofeedbackonitself.
Thegain.valuepropertycontrols theamountof theeffectandthedelayTime.valuepropertycontrolsthelengthofthedelay.Thefollowingcodeappliestheeffecttoanaudiobuffer.varsound=audioContext.createBufferSource();vardelayAmount=audioContext.createGain();vardelay=audioContext.createDelay();sound.buffer=audioBuffer;delay.delayTime.value=0.5;delayAmount.gain.value=0.5;sound.connect(delay);delay.connect(delayAmount);delayAmount.connect(delay);delayAmount.connect(audioContext.destination);sound.connect(audioContext.destination);sound.start(audioContext.currentTime);
CreatingSlapBackEffectsAslapbackeffectisaquickdelayof40–140ms.Tocreatethistypeofeffect,yousplitaninputsource and connect one branch to the delay and the other branch to the destination. You alsoconnectthedelaynodetoagainnodetocontrolthevolumeoftheeffect.Thenodeconfigurationforaslapbackisshowninthefollowingexampleandfigure.
varsound=audioContext.createBufferSource();vardelayAmount=audioContext.createGain();vardelay=audioContext.createDelay();sound.buffer=audioBuffer;delay.delayTime.value=0.06;delayAmount.gain.value=0.5;sound.connect(delay);delay.connect(delayAmount);delayAmount.connect(audioContext.destination);sound.connect(audioContext.destination);sound.start(audioContext.currentTime);
CreatingaPing-PongDelay
Aping-pongeffectisanechodelaywheretheechotogglesbetweentheleftandrightsideofthestereospectrum.Thiseffectcanbecreatedbyspittinganinputsourcetotheleftandrightoutputs,runningeachofthoseoutputsthroughindependentdelayandgainnodes,andthenfeedingbackthesignalfromthegaintoitsowndelayaswellasthedelaybeingusedtoprocesstheothersideofthestereospectrum.
Thefollowingcodeimplementstheconfigurationshownintheabovefigure.//___________________________________________________BEGINsetup
varsound=audioContext.createBufferSource();sound.buffer=audioBuffer;
varmerger=audioContext.createChannelMerger(2);varsplitter=audioContext.createChannelSplitter(2);
varleftDelay=audioContext.createDelay();varrightDelay=audioContext.createDelay();
varleftFeedback=audioContext.createGain();varrightFeedback=audioContext.createGain();
//____________________________________________________ENDsetup
sound.connect(splitter);sound.connect(audioContext.destination);
splitter.connect(leftDelay,0);leftDelay.delayTime.value=0.5;
leftDelay.connect(leftFeedback);leftFeedback.gain.value=0.6;leftFeedback.connect(rightDelay);
splitter.connect(rightDelay,1);rightDelay.delayTime.value=0.5;rightFeedback.gain.value=0.6;
rightDelay.connect(rightFeedback);rightFeedback.connect(leftDelay);
leftFeedback.connect(merger,0,0);rightFeedback.connect(merger,0,1);
//___________________________________________BEGINoutput
merger.connect(audioContext.destination);
//___________________________________________ENDoutput
sound.start(audioContext.currentTime);
SummaryThe delay node by itself is not complicated or difficult to use, butwhen combinedwith othernodesitcanbeapowerful toolfor thecreationof interestingaudioeffects.In thenextchapter,youwillcontinueexploringthenodegraphandlearnhowtoapplydynamicrangecompressiontoaudioinputsources.
19DynamicRangeCompression
In this chapter, youwill learn about the dynamics compressor node. This node allows you toapplydynamicrangecompressiontoaudioinputsources.
TheDynamicsCompressorNodeDynamicrangecompressionistheprocessofautomaticallyattenuatinganaudiosignalwhenitsdecibel level exceeds a specified threshold. This is analogous to manually turning down thevolumeknobonyourradiowhenapieceofmusicgetstooloudandthenturningitbackupduringaquieterpassage.Whenthisactionisdonewithadynamicrangecompressor,yougetthebenefitsofautomation,speed,andtheprecisionofacomputer.
TheWebAudio API comes with a built-in tool called the dynamics compressor node thatallowsyoutoapplydynamicrangecompressiontoaudioinputsources.Touseit,youmustfirstinvoke the createDynamicsCompressor() method and store the resulting object in avariable.varcompressor=audioContext.createDynamicsCompressor();
Theobjectprovidesyouwithacollectionoffivepropertiesthataffectthedynamicrangeofanaudioinputsource.Asixthpropertycalledreductionisalsoavailable,butitdoesnotaffectthe input source in anyway. The reduction property is used exclusively to output a reductionvalue. These properties are briefly described in the following chart. All properties exceptreduction take a number as their assignment. Thereduction property provides only areadoutvalue.
The following code demonstrates how to apply the dynamics compressor node to an audioinput source. In this example, for every 12 dB the signal surpasses a threshold of −40 dB, itsoutputisincreasedby1dB.//___________________________________________________BEGINsetup
varsound=audioContext.createBufferSource();varcompressor=audioContext.createDynamicsCompressor();sound.buffer=audioBuffer;
//____________________________________________________ENDsetup
sound.connect(compressor);compressor.threshold.value=-40;compressor.ratio.value=12;
//___________________________________________BEGINoutputcompressor.connect(audioContext.destination);//___________________________________________ENDoutputsound.start(audioContext.currentTime);
Anyone familiarwith theworld of creative audiowill immediately be familiarwith everyproperty available to the dynamics compressor node except one: reduction. Thereductionproperty,specifictotheWebAudioAPI,outputsanumericvaluerepresentingtheamount of reduction the compressor is imposing on the input source.The following code usessetInterval()toallowyoutoseethechangeinreductionvalueasanaudioinputsourceiscompressed.//___________________________________________________BEGINsetupvarcompressor=audioContext.createDynamicsCompressor();
varsound=audioContext.createBufferSource();sound.buffer=audioBuffer;
//____________________________________________________ENDsetup
sound.connect(compressor);compressor.threshold.value=-40;compressor.ratio.value=12;
//___________________________________________BEGINoutputcompressor.connect(audioContext.destination);//___________________________________________ENDoutput
sound.start(audioContext.currentTime);
window.setInterval(function(){console.log(compressor.reduction.value);},50);
SummaryUsing the dynamics compressor node is not complicated. It contains all the basic parametersneededtomodifythedynamicrangeofanyinputsourceconnectedtoit.
InthenextchapteryouwilllearnhowtoworkwithtimeintheWebAudioAPI.
20Time
Inthischapter,youwilllearnhowtoworkwithtimetoscheduleWebAudioAPIsoundplaybackpoints,howtocreateloops,andhowtoautomateparameterchanges.
TheTimingClockWhenyouinvokeanewinstanceoftheaudiocontext,theWebAudioAPI’sinternaltimingclockbegins to tick.This timing reference is in seconds and is expressed as a decimal number.Thetimingclockistiedtoyourcomputer’sinternalaudiohardwaresubsystem,givingitadegreeofprecisionthatcanalignwithsoundsatthesamplelevel.Ifyouwanttoseethecurrentvalueoftheaudioclock,youcanusethecurrentTimepropertyoftheaudiocontext.console.log(audioContext.currentTime);
Whenyouplayanaudioevent,theWebAudioAPIrequiresyoutoscheduleit.Rememberthatthe unit you use for time value scheduling is seconds. If you want to schedule an eventimmediately,youcanusethecurrentTimepropertyoftheaudiocontext.
You have already had some exposure to scheduling the playback of sounds in previouschapters,suchasinthefollowingexamplecode:sound.start(audioContext.currentTime);//Playimmediatelysound.start(audioContext.currentTime+2);/*Playaudiobuffertwosecondsintothefuture*/
ThestartMethodThestartmethodisusedtobeginasoundplaying.Thestartmethodtakesthreearguments.The first argument schedules when the sound plays, either immediately or in the future. Thesecond argument sets a start point that determineswhere to begin playback from in the audiobuffer.Thethirdargumentsetswhenasoundceasestoplay.Forareal-worldexample,imagine
youwereplayingbacka4/4drumloopand0.5secondsintotheloopthedrummerhitthesnaredrum.Ifyouwanttostartplaybackfromthispoint,yousetthesecondargumentofstart() to0.5.sound.start(audioContext.currentTime,0.5);
Thethirdargumentsetshowmuchofthesoundwillplay.Ifyouhaveasoundthatis4secondslongandyouonlywanttohearthefirst2seconds,thenyousetthethirdargumentto2.sound.start(audioContext.currentTime,0,2);
LoopingSoundsToloopsounds,yousetthelooppropertyofanaudiobuffersourcenodetotrue.Tosetthestartpointofaloop,youusethepropertyloopStart.Tosettheendpointofaloop,youusethepropertyloopEnd.sound.loop=true;sound.loopStart=1;/*Setlooppointatonesecondafterbeginningofplayback*/sound.loopEnd=2;/*Setloopendpointattwosecondsafterbeginningofplayback*/
Sometimeswhentryingtodiscernplaybackandlooppoints,itisusefultoknowthelengthofan audio file. You can get this information using a property of the sound buffer namedduration.
varsound=audioContext.createBufferSource();sound.buffer=buffer;sound.buffer.duration;//lengthinsecondsofaudiofile
Includedinthecodeexamplesforthischapterisanapplicationthatallowsyoutomodifytheplaybackandlooppointsofanaudiofileinrealtimeusinginteractivesliders.
UpdateYourAudioLoaderLibraryTheplay()methodofyouraudioloaderlibraryisnotdesignedtoaccessthesecondandthirdarguments of thestart method. You canmake these arguments availablewith the followingmodificationstoyourcode:soundObj.play=function(time,setStart,setDuration){playSound=audioContext.createBufferSource();playSound.buffer=soundObj.soundToPlay;playSound.connect(audioContext.destination);playSound.start(audioContext.currentTime+time||audioContext.currentTime,setStart||0,setDuration||soundObj.soundToPlay.duration);};
Thestartandendpointsettingsarenowavailable.sounds.snare.play(0,1,3);
ChangingAudioParametersoverTimeUp to thispointyouhavechangedaudioparametersbydirectly setting thevalueproperty to anumber.varosc=audioContext.createOscillator();osc.frequency.value=300;
TheWebAudioAPI comeswith a collection ofmethods that allow you to schedule audioparametervaluesimmediatelyoratsomepointinthefuture.Thefollowingcodeshowsalistofthesemethods.
setValueAtTime(arg1,arg2)exponentialRampToValueAtTime(arg1,arg2)linearRampToValueAtTime(arg1,arg2)setTargetAtTime(arg1,arg2,arg3)setValueCurveAtTime(arg1,arg2,arg3)
Youcanusethesemethodsinplaceofsettingthevaluepropertyofanaudioparameter.osc.frequency.value=100;//Setvaluedirectlyosc.frequency.setValueAtTime(arg1,arg2);/*Setvaluewithaudioparametermethod*/
TheAudioParameterMethodsThesetValueAtTimeMethodThesetValueAtTimemethodallowsyoutocreateanabruptchangeofanaudioparameteratafutureperiodintime.Thefirstargumentisthevaluetheparameterwillbechangedto,andthesecondargumentisthetimethatitwilltaketochangetothatvalue.Inthefollowingexample,5secondsafterthecodeisrun,againnodeparametervalueisabruptlychangedfrom1to0.1.varosc=audioContext.createOscillator();varvolume=audioContext.createGain();osc.connect(volume);volume.gain.value=1;volume.gain.setValueAtTime(0.1,audioContext.currentTime+5);osc.start(audioContext.currentTime);volume.connect(audioContext.destination);
To use any of the other audio parameter methods that are described next, you must firstinitialize their settings usingsetValueAtTime(). This is shown in the code examples foreachmethod.
TheexponentialRampToValueAtTimeMethodTheexponentialRampToValueAtTime()methodallowsyoutocreateagradualchangeof the parameter value. Unlike the abrupt change of setValue AtTime(), this methodfollowsanexponentialcurve.Thefollowingcodedemonstratesthisbychanginganoscillator’sfrequencyfrom200Hzto3kHzoverthecourseof3seconds.varosc=audioContext.createOscillator();varvolume=audioContext.createGain();osc.frequency.value=200;osc.frequency.setValueAtTime(osc.frequency.value,audioContext.currentTime);//____Setinitialvalues!osc.frequency.exponentialRampToValueAtTime(3000,audioContext.currentTime+3);osc.start(audioContext.currentTime);osc.connect(audioContext.destination);
ThelinearRampToValueAtTimeMethodThe linearRampToValueAtTime method is similar toexponentialRampToValueAtTime() but follows a gradual linear curve instead of anexponentialcurve.varosc=audioContext.createOscillator();varvolume=audioContext.createGain();osc.frequency.value=200;osc.frequency.setValueAtTime(osc.frequency.value,audioContext.currentTime);//Setinitialvaluesosc.frequency.linearRampToValueAtTime(3000,audioContext.currentTime+3);osc.start(audioContext.currentTime);osc.connect(audioContext.destination);
ThesetTargetAtTime()MethodThesetTargetAtTime()methodtakesthreearguments.Thefirstargumentisthefinalvalueof the audio parameter, the second argument is the time the change will begin, and the thirdargumentisatimeconstantthatdetermineshowlongthechangewilltaketocomplete.Thelargerthenumberofthethirdargument,thelongerthechangetakestocomplete.varosc=audioContext.createOscillator();varvolume=audioContext.createGain();osc.frequency.value=200;osc.frequency.setValueAtTime(osc.frequency.value,audioContext.currentTime);//Setinitialvaluesosc.frequency.setTargetAtTime(3000,audioContext.currentTime,2);osc.start(audioContext.currentTime);osc.connect(audioContext.destination);
ThesetValueCurveAtTime()MethodThesetValueCurveAtTime() method allows you to create a custom curve based on acollectionofaudioparametervaluesstoredinanarray.Thismethodtakesthreearguments.Thefirst argument is an array of values. The array used is a special kind of array called afloat32Array(), which is a typed array. Typed arrays are better performing thanconventional arrays and allow someWebAudioAPIs towork directlywith binary data. Thesyntaxforafloat32Array() requiresyou toexplicitlyset thenumberof indexvaluesandlookslikethefollowingcode:varwaveArray=newFloat32Array(10);//__SetnumberofindexvalueswaveArray[0]=20;waveArray[1]=200;waveArray[2]=20;waveArray[3]=200;waveArray[4]=20;waveArray[5]=200;
waveArray[6]=20;waveArray[7]=200;waveArray[8]=20;waveArray[9]=200;
Thesecondargumentrepresentswhenyouwantthechangestobegin,andthethirdargumentisthetimespanyouwantthechangestotakeplacewithin.Thefollowingcodedemonstratesthisbytogglingthefrequencyofanoscillatorfrom100to500Hzandbackagainoverthecourseof3seconds.Thiscreatesawobbleeffect.varwaveArray=newFloat32Array(10);waveArray[0]=100;waveArray[1]=500;waveArray[2]=100;waveArray[3]=500;waveArray[4]=100;waveArray[5]=500;waveArray[6]=100;waveArray[7]=500;waveArray[8]=100;waveArray[9]=500;
varosc=audioContext.createOscillator();varvolume=audioContext.createGain();osc.frequency.value=500;osc.frequency.setValueAtTime(osc.frequency.value,audioContext.currentTime);//Setinitialvaluesosc.frequency.setValueCurveAtTime(waveArray,audioContext.currentTime+1,3);osc.start(audioContext.currentTime);osc.connect(audioContext.destination);
SummaryInthischapter,youlearnedthefundamentalsofworkingwithtime.Youlearnedhowtoloopandschedule sound playback, as well as how to schedule parameter value changes. In the nextchapter,youwilllearnhowtocreateaudiovisualizationsusingtheAnalysernode.
21CreatingAudioVisualizations
Inthischapter,youwilllearnhowtousetheAnalysernodetocreateaspectrumanalyzerthatdisplaysreal-timeamplitudeinformationofaudiosignalsacrossacollectionoffrequencybands.TheWebAudioAPIincludesanodenamedAnalyser thatgivesyoureal-timefrequencyandtimedomaininformationaboutaudioinputsources.Thisinformationcanbeusedtocreatecustomvisual representationsof audio signals that include (but arenot limited to) spectrumanalyzers,phasescopes,andwaveformrenders.
ABriefWordonFourierAnalysisBeforeyougetstarted,youmustfirsthaveabasicconceptualunderstandingofFourieranalysis.Fourieranalysisisadifficulttopicinvolvingalotofimpressivemath,soapropercoverageofthe topic iswell beyond the scopeof thisbook.Themainpointyouneed tounderstand is thatFourieranalysisisawaytotakeamazinglycomplexthingslikesoundwavesandsimplifythem.Withthisapproach,asignal(referredtoasafunction)canbeeitherrepresentedorapproximatedby a combination of simpler periodic signals or functions, such as sine and cosine waves.Replicating a sound is also theoretically possible by combining an infinite number of thesewaveforms. In theory, a perfect replica (or perfect separation of constituent parts) is realizedfromthiscombination,butinpractice,humanbeingslackinfinitetimeandcomputingpower,soyouwillalwayshavetomakedowithanapproximation.
Some of the most common forms of Fourier analysis in audio processing are called fastFouriertransforms,ormorecommonlyFFTs.ThegoalofanFFTistoquicklygiveyouausefulapproximationwithoutdoingtoomuchcomputationalworkandslowingdownthesystem.
ABriefExplanationofBinary-CodedDecimalNumbers
TobetterunderstandhowtheWebAudioAPIgivesyouaccesstothetimeandfrequencydomainof audio input sources, youneed to understandhow to read binary-codeddecimal numbers.Abinary system is composed of a series ofon andoff values called bits.Bits are read in 8-bitgroupingscalledbytes,whichhaveanequivalentdecimalvalue.Bitsarereadfromrighttoleftandeachvalueiseitheranonvaluerepresentedbya1,oranoffvaluerepresentedbya0.Thedecimalequivalentofagroupingofbitsiscalculatedbyexponentiallycountingfromrighttoleftandaddingalloftheonvaluestogether.
Whenallbitsareon,abytehasavalueof255.Thisallowsfor256totalpossiblevalues(0–255).
TheSpectrumAnalyzerThe following program creates a basic frequency spectrum analyzer using an oscillator as itsinputsource.Therestofthischapterisdedicatedtoexplaininghowthiscodeworks.
JavaScript/JQuery"usestrict";$(function(){varaudioContext=newAudioContext();varanalyzer=audioContext.createAnalyser();varosc=audioContext.createOscillator();varfrequencyData=newUint8Array(analyzer.frequencyBinCount);//___Createarrayanalyzer.getByteFrequencyData(frequencyData);//______________________Storefrequencydataconsole.log(frequencyData.length);console.log(frequencyData);
varapp=$(".app");
varbars=undefined;
osc.frequency.value=120;osc.connect(analyzer);analyzer.connect(audioContext.destination);osc.start(audioContext.currentTime);analyzer.fftSize=2048;console.log(analyzer.frequencyBinCount);//1024
//________________________________BEGINVisualization
$(".bin-count-number").text(analyzer.fftSize/2);//____Bincount
for(vari=0;i<analyzer.frequencyBinCount;i++){
$(".app").append("<div></div><span>"+i+"</span>");}
bars=$(".app>div");
functionupdate(){requestAnimationFrame(update);
analyzer.getByteFrequencyData(frequencyData);
for(vari=0;i<bars.length;i+=1){
bars[i].style.height=frequencyData[i]+'px';
}
}
update();
//_______________________________ENDvisualization
});
HTML<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title></title><scripttype="text/javascript"src="js/jquery.js"></script><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"type="text/css"></head><!--______________________________________________________BEGINAPP-->
<body><pclass="bin-count">Bincount:<bclass="bin-count-number"></b></p>
<divclass="app"></div></body><!--______________________________________________________ENDAPP--></html>
CSS.app{
position:relative;margin:10px;
}
.app>div{width:0.1px;background-color:orange;display:inline-block;outline-style:solid;outline-color:orange;outline-width:0.1px;margin-left:8px;
}
span{display:inline-block;font-size:14px;color:rgba(128,128,128,0.5);margin:2px;
}
.bin-count{position:absolute;left:30%;float:right;font-size:2em;height:50px;
}
Theoutputoftheapplicationlookslikethefollowingfigure.
WalkingthroughtheCodeThe first step to creating a spectrum analyzer is to invoke Analyser() and connect inputsources to the returned object. For this application, the only input source used is anoscillator and the output of the analyser variable is connected directly toaudioContext.destination.varanalyzer=audioContext.createAnalyser();varoscillator=audioContext.createOscillator90;oscillator.connect(analyzer);analyzer.connect(audioContext.destination);
TheAnalyserinterfaceenablesyoutoperformvariousFFTsontheaudiostream.TheFFTusedtocreateaspectrumanalyzertransformsthetimedomainoftheaudiosignalintonormalized(orlimited)frequency-domaindata.Thisisdonebychoppingtheoriginalaudiosignalintoparts,typicallycalledbins,andperformingananalysisandtransformationoneachpart.
The size of the FFT is stored in thefftSize property of theAnalyser node and thedefaultvalueis2048.Theallowedvaluesareanypowerof2between32and2048.Ifyousetitwrong,youwillgetanerror.ThenumberofbinsavailableisonehalfofthefftSizepropertyand is accessible by a read-only property of the Analyser node namedfrequencyBinCount.
analyzer.fftSize=2048;console.log(analyzer.frequencyBinCount);//1024...orhalfoffftSize
Each bin is designated a range of frequencies called a band, and the following formuladeterminestherangeofeachband:
(SampleRate)/(FFTSize)=(BandSize)
Example:
44,100/2048=21.533203125
StoringtheFrequencyDatainanArrayThenextstep is tocreateanarray tostore thefrequencydata.Aspecialkindofarraycalledatypedarrayisrequiredforthistask.Atypedarrayisanarray-likeobjectspecificallydesignedforworkingwithbinarydata.//______________________CreatetypedarrayvarfrequencyData=newUint8Array(analyzer.frequencyBinCount);
//______________________Storefrequencydataanalyzer.getByteFrequencyData(frequencyData);
There are two kinds of typed arrays that the Analyser node is designed to work with:Float32Array andUint8Array. The index values of aFloat32Array are always adecimalnumberbetween0and1.TheindexvaluesofaUint8Arrayarelimitedto8bitsofinformationandwillalwaysbeanintegerbetween0and255.UsingaFloat32Arrayallowsforupto32bitsofinformationandgivesyoumoreprecisionbutismoreresourceintensive.Thisis in contrast to Uint8Array, which is more resource efficient but less precise. ThisapplicationusesaUint8Array.AFloat32ArrayorUint8Arraymustbecreatedusingthekeywordnew.
Inthefollowingcode,theUint8Arrayisinvokedwithasingleargumentthatdeterminesthenumberofindexesitwillhavebyusinganalyzer.frequencyBinCount.varfrequencyData=newUint8Array(analyzer.frequencyBinCount);console.log(frequencyData.length);//1024
YounowstorethefrequencydomaindatainthearrayusinggetByteFrequencyData().
analyzer.getByteFrequencyData(frequencyData);
IfthefrequencyDataarrayisaFloat32ArrayinsteadofaUint8Array,youusethegetFloatFrequencyData()methodandthecodelookslikethis:analyzer.getFloatFrequencyData(frequencyData);
HowtoThinkAboutthefrequencyDataArrayEach indexvalueof thefrequencyData arraycanbeanynumberbetween0and255.Thisvalue is correlated with the energy intensity of the frequency band that is designated by thatparticulararrayindex.Thefollowingdiagramscanhelptoclarifythis.
BuildingtheDisplayInterfaceThe following line of code renders the current number of bins to theDocumentObjectModel(DOM).$(".bin-count-number").text(analyzer.fftSize/2);//____Bincount
Thefollowingforloopcreatesadivforeachbin.Insideeachdiv,aspaniscreatedthatdisplaysanumberthatcorrespondstoeachbin.for(vari=0;i<analyzer.frequencyBinCount;i++){
$(".app").append("<div></div><span>"+i+"</span>");
}
Thebarsvariableselectsalldivelementsandisusedlaterinthecode.bars=$(".app>div");
ConnectingtheAnalyzertotheDOMToreadandusefrequencyData,theprogrammustcontinuouslycheckitscurrentstatesothatthe DOM can be updated with the new information. This can be done by placinganalyzer.getByteFrequencyData(frequencyData) in therequestAnimationFrame function.requestAnimationFrame is a method that tellsthe browser that you wish to perform an animation.When it is time to update the animation,requestAnimationFramecallsthefunctionthatyoupassedtoit.Theupdateratematchesthedisplayrefreshrateofthewebbrowser.functionupdate(){requestAnimationFrame(update);analyzer.getByteFrequencyData(frequencyData);}
update();
Tocreatetheverticalfrequencybars,thefor loopupdatestheCSSheightpropertyofeachdiv stored in thebar variable.Thevaluegiven toeachdiv is a pixel representationof thecurrentfrequencyDataarrayindex.Thisvalueisbetween0and255px.
functionupdate(){requestAnimationFrame(update);analyzer.getByteFrequencyData(frequencyData);
for(vari=0;i<bars.length;i+=1){
bars[i].style.height=frequencyData[i]+'px';
}
}
update();
Theuserinterfaceofthespectrumanalyzerisdesignedtocreateadivforallbins.YoucanchangethesizeoftheFFTtolowerthebincount.analyzer.fftSize=64;
Theapplicationyoucreatedinthischapterworkswithfrequency-domaindata.Ifyouwanttoworkwithtime-domaindata,theAnalysernodehastwomethodsthatletyoucopyittoatypedarray. The first method is getByteTime DomainData () and is for use with aUint8Array(). The second method is getFloatTimeDomainData() and is for usewithFloat32Array().Tostore the timedomaindata inaUint8Array(),youwrite thefollowingcode:varfrequencyData=newUint8Array(analyzer.frequencyBinCount);analyzer.getByteTimeDomainData(frequencyData);
SummaryInthischapter,youlearnedabouttheAnalysernodeandcreatedafrequencyspectrumanalyzerapplication.
Upuntilnow,whenaddingnewnodesthataffectaudiobuffers,youhaveappliedthechangesto a single node graph that affectsall audio buffer input sources. In the next chapter, youwillupdatetheaudioloaderlibrarythatyoucreatedinChapter13toallowuserstocreatecustomizednodegraphsforindividualaudiobuffers.
22AddingFlexibilitytotheAudioLoaderAbstraction
In this chapter, you will add flexibility to the audio loader abstraction and give usersindependently customizable nodegraphs for audio buffer input sources. In its current state, theaudio loader library you created in Chapter 13 only allows you to create one universal nodegraph configuration. So any files that you load have to conform to this configuration. This isundesirablefortworeasons.Thefirstisthatwhenyoucreatealibrary,youdon’twanttheusertohavetomodifyitsinternalstogetthefunctionalitytheywant.Thesecondreasonisthatitisusefultohave thechoice toapplycompletelydifferent effects todifferent audio input sources,whichrequiresnodeconfigurationsthatareindependentlycustomizable.
TheUpdatedInterfaceWhenyoucreateanylibraryorabstraction,itishelpfultofirstdefinewhattheinterfacewilllooklikeandthenworkbackwardtowarditscreation.Inthiscase,thefollowingexampleshowshowthe final interface will look. This is similar to the current library except that the object thataudioBatchLoadertakesasanargumenthasamethodthatdefinesacustomnodegraph.Thisnodegraphisappliedtoallaudiofilesreferencedaspropertiesinthecontainingobject."usestrict";
varsoundData={kick:"sounds/kick.mp3",snare:"sounds/snare.mp3",//________________________________________BEGINcustomnodegraphnodeGraph:functionnodeGraph(sound){vargain=audioContext.createGain();gain.gain.value=1;sound.connect(gain);gain.connect(audioContext.destination);}//________________________________________ENDcustomnodegraph}
varsound=audioBatchLoader(soundData);//Takestheobjectasargumentsound.kick.play();//Soundplaysusingcustomnodegraphsound.snare.play();//Soundplaysusingcustomnodegraph
Thisinterfaceallowsyoutoaddonemethodtotheobject;andthismethodthensetsthenodegraphconfigurationforallsoundfilesreferencedaspropertiesofthatobject.
ModifyingtheLibraryMakeacopyoftheemptytemplatefolderyoucreatedinChapter1andrenameittoChapter22.Inthejsfolder,placeacopyofthecompletedaudioloaderlibraryyoumodifiedinChapter20andreferenceitfromtheindex.htmlfile.<head><metacharset="UTF-8"><title>app</title><scriptsrc="js/audiolib.js"></script><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"></head>
Next,createadirectorynamed“sounds”andplaceanMP3filenamed“snare”init(thisfileisavailableinthedownloadablecodeexamples).Copythefollowingcodeintotheapp.jsfile."usestrict";varsound=audioFileLoader("sounds/snare.mp3",function(sound){vargain=audioContext.createGain();gain.gain.value=0.2;sound.connect(gain);gain.connect(audioContext.destination);
});
window.addEventListener("mousedown",function(){sound.play();
});
window.addEventListener("mouseup",function(){sound.stop();
});
Ifyourunthepreviouscode,itwillnotwork.Intheaudiolib.jsfileyouwillnowmodifythefunctionnamedaudioFileLoadersothatthepreviouscodeworks.Thesemodificationswillletusersloadsinglefiles,eachofwhichhasauniquenodegraph.Oncethisworks,wewillgooverhowtomodifyaudioBatchLoadertoloadmultiplefiles.
Inyouraudiolib.jsfile,modifythecodetoincludeacallbackfunctionlikethefollowingexample:
varaudioContext=newAudioContext();functionaudioFileLoader(fileDirectory,callback){varplaySound=undefined;varsoundObj={};
soundObj.fileDirectory=fileDirectory;vargetSound=newXMLHttpRequest();getSound.open("GET",soundObj.fileDirectory,true);getSound.responseType="arraybuffer";getSound.onload=function(){audioContext.decodeAudioData(getSound.response,function(buffer){soundObj.soundToPlay=buffer;
});}
getSound.send();
soundObj.play=function(time){playSound=audioContext.createBufferSource();playSound.buffer=soundObj.soundToPlay;playSound.connect(audioContext.destination);playSound.start(audioContext.currentTime+time||audioContext.currentTime,setStart||0,setDuration||soundObj.soundToPlay.duration);callback(playSound);
}
soundObj.stop=function(time){
playSound.stop(audioContext.currentTime+time||audioContext.currentTime);
}returnsoundObj;};
Inthepreviouscode,thecallbacknowfulfillstheroleofacustomizablenodegraphandthecodewillnowwork.However,thereisstilloneproblem:Iftheuserdoesnotuseacallback,thenanerrorresults.varsound=audioFileLoader("sounds/snare.mp3");//ERROR!
This error can be dealt with by simply using a conditional to check if the callback is afunction.Iftheconditionalreturnsfalse(becausetheuserdidn’tsetit),adefaultnodegraphissetinitsplace.
Todothis,modifythecodeasfollows:soundObj.play=function(time){playSound=audioContext.createBufferSource();playSound.buffer=soundObj.soundToPlay;playSound.start(audioContext.currentTime+time||audioContext.currentTime);
callback(playSound);if(typeofcallback==="function"){returncallback(playSound);
}else{returnplaySound.connect(audioContext.destination);}
}
Thiscodenowworkswhetherthefunctionisinvokedwithacallbackornot.
ModifyingaudioBatchLoaderYouwillnowedittheaudioBatchLoaderfunctiontocheckifitsparameterobjectcontainsamethod, and if it does, themethod is set as the callback ofaudioFileLoader. This codeappliesacustomnodegraphtoagroupoffiles.functionaudioBatchLoader(obj){varcallback=undefined;varprop=undefined;
for(propinobj){if(typeofobj[prop]==="function"){callback=obj[prop];deleteobj[prop];}}
for(propinobj){
obj[prop]=audioFileLoader(obj[prop],callback);//___Placefunctionascallback
}returnobj;}
AnExplanationofthePreviousCodeEditIfamethodisfoundonobj,itisassignedtothevariablenamedcallback.Themethodisthendeleted from obj using the delete keyword. The deletion is necessary so that theaudioFileLoaderdoesnotattempttoreferenceitasanaudiofiledirectory.
Thefollowingcodeisanexampleofloadingacollectionoffileswithacustomnodegraph.Thiscodenowworks."usestrict";
varsound=audioBatchLoader({snare:"sounds/snare.mp3",kick:"sounds/kick.mp3",hihat:"sounds/hihat.mp3",nodes:function(sound){vargain=audioContext.createGain();
sound.connect(gain);gain.gain.value=0.5;gain.connect(audioContext.destination);
}});
window.addEventListener("mousedown",function(){sound.snare.play();});
window.addEventListener("mouseup",function(){sound.snare.stop();});
OnethingyoushouldbeawareofisthattheobjectthattheaudioBatchLoadertakesasanargumentisintendedtohaveonlyonemethod.Ifithasmorethanonemethod,thenoneofthemisoverwritten.Whenusingafor-inloop,thepropertiesandmethodsofthetargetedobjectarenotreturned in any particular order. Because of this, you cannot knowwhichmethod is used andwhichoneisoverwrittenuntilthesoundisplayedback.Forthisreason,youmightwanttowriteanerrorchecktothrowanerrorforargumentobjectsthathavemorethanonemethod.Thisisleftuptoyoutoimplement.
SummaryIn thischapter,youaddedadditional flexibility toyouraudio loader libraryand in theprocessyou were exposed to a real-world example of how callback functions can be useful whendesigningalibrary.Inthenextchapteryouwilllearnhowtobuildaninteractivemusicsequencer.
23BuildingaStepSequencer
Music applications, like sequencers and drummachines, allow users to record, edit, and playbacksoundsasacollectionoforganizednotearrangements.DuetothenatureoftheWebAudioAPIanditsrelationshiptotheDOM,musicsequencingapplicationsareachallengetocreate.Inthischapter,youwilllearnwhythisissoandhowtomeetthechallengebybuildingabasicdrumpatternstepsequencer.
TheProblemTheWebAudioAPIletsyouscheduleeventsimmediatelyorinthefuture.Aproblemwiththisapproach is that once an event is scheduled, it cannot be unscheduled. So for example, thefollowingcodeschedulesthreedrumsoundstoplayinan8thnotepatternforfourbars.varkick=audioFileLoader("sounds/kick.mp3");varsnare=audioFileLoader("sounds/snare.mp3");varhihat=audioFileLoader("sounds/hihat.mp3");
vartempo=120;//_____BPM(beatsperminute)vareighthNoteTime=(60/tempo)/2;functionplayDrums(){//Play4barsofthefollowing:for(varbar=0;bar<4;bar++){vartime=bar*8*eighthNoteTime;//Playthebass(kick)drumonbeats1,5kick.play(time);kick.play(time+4*eighthNoteTime);
//Playthesnaredrumonbeats3,7snare.play(time+2*eighthNoteTime);snare.play(time+6*eighthNoteTime);//Playthehi-hateveryeighthnote.for(vari=0;i<8;++i){hihat.play(time+i*eighthNoteTime);}}}
Ifyouwanttochangethetemporelationshipofthesesoundsinthemiddleofthefourbars,youcan’t. Instead, you have to wait until the sounds have completed playing. This is true of anyscheduled events that you might want to change during playback. And this restriction is notrelegatedtojusttempochanges.
CanIUsesetIntervalorsetTimeout?YoumightbewonderingifyoucanusesetIntervalorsetTimeouttosolvethisproblem.ThefollowingcodeusessetIntervaltoincrementacounterataspecifiedbeatsperminute(BPM), anddependingon the countervalue, aparticulardrumsound isplayed.This creates arhythmicpattern.varkick=audioFileLoader("sounds/kick.mp3");varsnare=audioFileLoader("sounds/snare.mp3");varhihat=audioFileLoader("sounds/hihat.mp3");
vartempo=120;//_____BPM(beatsperminute)varmilliseconds=1000;vareighthNoteTime=((60*milliseconds)/tempo)/2;
varcounter=1;window.setInterval(function(){if(counter===8){counter=1;}else{counter+=1;}if(counter){hihat.play();}if(counter===3||counter===7){snare.play();}if(counter===1||counter===5){kick.play();}
},eighthNoteTime);
TheproblemwiththisapproachisthatboththesetTimeoutandsetIntervalmethodshavetimingsthatareimpreciseandunstable.Therearetworeasonsforthis.Thefirstisthatthesmallest unit of time available to these methods is an integer of 1 millisecond, which is notpreciseenoughforaudiosample-levelvalueslike44.100kHz.TheotherproblemisthatunliketheWebAudioAPItimingclock,thesemethodscanbeinterruptedbyancillarybrowseractivitylikepagerenderingandredraws.AlthoughyoumightexpectsetIntervalorsetTimeouttorunateverynthmillisecond,dependingonfactorsoutsideyourcontrol,thevaluewilllikelybelargerandaudiblynoticeable.
TheSolutionThesolutiontotheproblemistocreatearelationshipbetweentheWebAudioAPItimingclockand the browser’s internal setTimeout method to create a look-ahead mechanism thatrecursivelyloopsandchecksifeventswillbescheduledatsometimeinthefuture.Ifthisisthecase,theschedulinghappensandtheevent(s)takesplace.Thisgivesyouenoughleewaytocanceleventsatthelastmomentifneeded.
OnethingtokeepinmindisthatbecausesetTimeoutisinherentlyunstable,weknowthatthisrelationshipwillalwayshaveanunstableaspecttoit.Whetherornotthisapproachisstableenoughforyourapplicationsisforyoutodecide.OnethingwecanbecertainofisthatitismuchmoreaccuratethanusingsetIntervalorsetTimeoutonitsown.
HowItWorksThe basis for the relationship between the Web Audio API timing clock and the browser’sinternalsetTimeoutmethodisexpressedinthefollowingcode:
varaudioContext=newAudioContext();varfutureTickTime=audioContext.currentTime;functionscheduler(){if(futureTickTime<audioContext.currentTime+0.1){futureTickTime+=0.5;//____canbeanytimevalue.0.5happenstobeaquarternoteat120bpmconsole.log(futureTickTime);}window.setTimeout(scheduler,0);}scheduler();
Theway thepreviouscodeworks is that thesetTimeout function loops recursively, anduponeachiteration,aconditionalcheckswhether thevalueoffutureTickTime iswithinatenth of a second of the audioContext.currentTime. If this evaluates to true thenfutureTickTimeisincrementedby0.5,whichishalfasecond in“WebAudioTime.”ThefutureTickTimevariableremainssetatthisvalueuntilaudioContext.currentTime“catches up with it” once again. Then within a tenth of a second, futureTickTime isincrementedbyahalf-secondintothefuture.Thispatterncontinuesforaslongasthefunctionisallowedtorun.
Becauseahalf-secondtranslatestoaquarternoteat120beatsperminute,thefollowingcodeusesthisinformationtocreatea1/4thnotetimingcountthatisloggedtotheconsole.varfutureTickTime=audioContext.currentTime;varcounter=1;functionscheduler(){if(futureTickTime<audioContext.currentTime+0.1){console.log("Thisisbeat:"+counter);futureTickTime+=0.5;/*____canbeanytimevalue.0.5happenstobeaquarternoteat120bpm*/
counter+=1;if(counter>4){counter=1;}}window.setTimeout(scheduler,0);}scheduler();
Thefollowingcodebuildsonthepreviousexampleandplaysanoscillatoroneachcount.Theoscillator is connected to a gainnodenamedmetronomeVolumewhich is connected to thedestination.Thegainnodeisaddedbecausethefinalapplicationinthischapterusesittotoggletheoscillatorvolumeonandoff.varfutureTickTime=audioContext.currentTime;varcounter=1;varosc=audioContext.createOscillator();varmetronomeVolume=audioContext.createGain();functionplayMetronome(time){osc=audioContext.createOscillator();osc.connect(metronomeVolume);metronomeVolume.connect(audioContext.destination);osc.start(time);osc.stop(time+0.1);
}
functionscheduler(){if(futureTickTime<audioContext.currentTime+0.1){
console.log("Thisisbeat:"+counter);playMetronome(futureTickTime);futureTickTime+=0.5;//____canbeanytimevalue.0.5happens//____________________________tobeaquarternoteat120bpm
counter+=1;
if(counter>4){counter=1;}}window.setTimeout(scheduler,0);}scheduler();
ChangingTempoIfyouwant tochange the tempo,youhave tochange the timerelationshipbetweenevents.YoucandothisbyalteringwheneventsarescheduledtostartwiththefutureTickTimevariable.Thefollowingformulaisusefulforconvertingbeats(quarternotes)toseconds.vartempo=120.0;//tempo(inbeatsperminute);varsecondsPerBeat=(60.0/tempo);
Theapplicationyoubuildassumestheuseofa16thnotegrid.Youcandesignitwithanybeatdivision(s) you want, but for simplicity it is hard-coded with 16 notes. The following codeconvertsthefutureTickTimevariablefromatimevaluethatrepresentsaquarternotetoatimevaluethatrepresentsa16thnote.Theoscillatorisalsomodifiedtoplayadifferentfrequencyonthedownbeat.varfutureTickTime=audioContext.currentTime;varcounter=1;vartempo=120;varsecondsPerBeat=60/tempo;varcounterTimeValue=(secondsPerBeat/4);//___16thnotevarosc=audioContext.createOscillator();varmetronomeVolume=audioContext.createGain();
functionplayMetronome(time){osc=audioContext.createOscillator();osc.connect(metronomeVolume);metronomeVolume.connect(audioContext.destination);osc.frequency.value=500;osc.start(time);osc.stop(time+0.1);
}functionscheduler(){if(futureTickTime<audioContext.currentTime+0.1){
console.log("Thisis16this:"+counter);playMetronome(futureTickTime);futureTickTime+=counterTimeValue;
if(counter===1){osc.frequency.value=500;}else{osc.frequency.value=300;}
counter+=1;
if(counter>16){counter=1;}}window.setTimeout(scheduler,0);}
scheduler();
Youcannowchangethetempobymodifyingthetempovariable.
BuildingtheSequencerYouarenowgoingtobuildthesequencerapplication.CreateacopyoftheWebAudiotemplatefolderyoucreatedinChapter1andrenameittosequencer.Intheindex.htmlfile,referenceboththeJQuerylibraryandtheaudiolib.js file thatyouupdated inChapter20. Inside thesequencerfolder,createafoldernamedsoundsandplacetheaudiofilesforthesequencerapplicationinit.
Copythefollowingcodetoapp.jsandsavethefile.Thiscoderefactorsthepreviouscodeyouhavewritten.Thisversionismorereadableandthemetronomeisgivenitsownfunction."usestrict";varaudioContext=newAudioContext();varfutureTickTime=audioContext.currentTime,counter=1,tempo=120,secondsPerBeat=60/tempo,counterTimeValue=(secondsPerBeat/4),osc=audioContext.createOscillator(),metronomeVolume=audioContext.createGain();
//_____________________________________________BEGINmetronomefunctionplayMetronome(time,playing){if(playing){osc=audioContext.createOscillator();osc.connect(metronomeVolume);metronomeVolume.connect(audioContext.destination);osc.frequency.value=500;if(counter===1){osc.frequency.value=500;}else{osc.frequency.value=300;}osc.start(time);osc.stop(time+0.1);}}
//______________________________________________ENDMetronomefunctionplayTick(){console.log("Thisis16thnote:"+counter);counter+=1;futureTickTime+=counterTimeValue;if(counter>16){counter=1;
}}
functionscheduler(){if(futureTickTime<audioContext.currentTime+0.1){playMetronome(futureTickTime,true);playTick();}window.setTimeout(scheduler,0);}scheduler();
PlayingBackSoundsinSequenceYou will now create a series of arrays that represent music sequencer tracks. Each of thesearraysstorescountervalues.Oneachiterationofthecounter,aforlooprunstocheckifanyofthearraysholdsthecurrentcountervalue.Ifanyofthemdoes,thesoundassociatedwiththatarrayplays. The arrays are associated with the correct sound through a function namedscheduleSound().Thisfunctiontakesfourarguments:
■Thetrackarray
■Thesoundtoplay
■Thecurrentcountvalue
■Thetimetoschedulethesound
Thetrackarraysarepopulatedwithvaluessothatyoucanhearadrumsequenceimmediately.varfutureTickTime=audioContext.currentTime,counter=1,tempo=120,secondsPerBeat=60/tempo,counterTimeValue=(secondsPerBeat/4),osc=audioContext.createOscillator(),metronomeVolume=audioContext.createGain();
/*_____________________________________________BEGINloadsoundsamples*/
varkick=audioFileLoader("sounds/kick.mp3");varsnare=audioFileLoader("sounds/snare.mp3");varhihat=audioFileLoader("sounds/hihat.mp3");varshaker=audioFileLoader("sounds/shaker.mp3");
//_____________________________________________ENDloadsoundsamples
//_____________________________________________BEGINArrayTracks
varkickTrack=[1,9,11],snareTrack=[5,13],hiHatTrack=[13,14,15,16],shakerTrack=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16];
//_____________________________________________ENDArrayTracks
functionscheduleSound(trackArray,sound,count,time){
for(vari=0;i<trackArray.length;i+=1){if(count===trackArray[i]){sound.play(time);}}}
//_____________________________________________BEGINmetronomefunctionplayMetronome(time,playing){
if(playing){osc=audioContext.createOscillator();osc.connect(audioContext.destination);osc.frequency.value=500;if(counter===1){osc.frequency.value=500;}else{osc.frequency.value=300;}osc.start(time);osc.stop(time+0.1);}}
//______________________________________________ENDMetronome
functionplayTick(){
console.log("Thisis16thnote:"+counter);counter+=1;futureTickTime+=counterTimeValue;if(counter>16){counter=1;}
}
functionscheduler(){if(futureTickTime<audioContext.currentTime+0.1){playMetronome(futureTickTime,true);
scheduleSound(kickTrack,kick,counter,futureTickTime-audioContext.currentTime);scheduleSound(snareTrack,snare,counter,futureTickTime-audioContext.currentTime);scheduleSound(hiHatTrack,hihat,counter,futureTickTime-audioContext.currentTime);scheduleSound(shakerTrack,shaker,counter,futureTickTime-audioContext.currentTime);
playTick();}window.setTimeout(scheduler,0);}
scheduler();
Youmightbewonderingwhy thescheduleSound() function invocationsaresubtractingtheaudioContext.currentTimefromthefutureTickTime().
scheduleSound(kickTrack,kick,counter,futureTickTime-audioContext.currentTime);
This is done because the audio library you built in Chapter 13 is designed to referenceaudioContext.currentTimebydefaultandaddsanyadditionalnumericargumentstothisvalue.YousubtractaudioContext.currentTimefromfutureTickTimebecausethesevalueswillbecombinedwhentheplay()methodofyourlibraryisinvoked.
Whenscheduler() is invoked, thedrumsequencedoesnot start immediatelybecause ittakestimefortheaudiobuffersandfilestoload.Thisbehaviorcanbecorrectedbymodifyingthecodesothattheschedulerisinitiatedbyaplay/stopbutton.InyourHTMLcode,createabuttonwithaclassofplay-stop-buttonandgiveittextofplay/stop.<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title></title><scriptsrc="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.js"></script><scriptsrc="js/audiolib.js"></script><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"></head>
<!--___________________________________________BEGINAPP--><body>
<!--HTMLcode--><buttonclass="play-stop-button">Play/Stop</button>
</body><!--____________________________________________ENDAPP--></html>
YounowuseJQuerytointerfacewiththeDOMandhavetowrapyourcodeinadocument-readyfunction.Thefollowingcodedefinestheplay/stopbuttonfunctionality.$(function(){varfutureTickTime=audioContext.currentTime,counter=1,tempo=120,secondsPerBeat=60/tempo,counterTimeValue=(secondsPerBeat/4),osc=audioContext.createOscillator(),metronomeVolume=audioContext.createGain(),timerID=undefined,isPlaying=false;
/*_____________________________________________BEGINloadsoundsamples*/
varkick=audioFileLoader("sounds/kick.mp3");varsnare=audioFileLoader("sounds/snare.mp3");varhihat=audioFileLoader("sounds/hihat.mp3");varshaker=audioFileLoader("sounds/shaker.mp3");
/*_____________________________________________ENDloadsoundsamples*/
//_____________________________________________BEGINArrayTracks
varkickTrack=[1,9,11],snareTrack=[5,13],hiHatTrack=[13,14,15,16],shakerTrack=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16];
//_____________________________________________ENDArrayTracks
functionscheduleSound(trackArray,sound,count,time){
for(vari=0;i<trackArray.length;i+=1){if(count===trackArray[i]){sound.play(time);}}
}
//_____________________________________________BEGINmetronome
functionplayMetronome(time,playing){
if(playing){osc=audioContext.createOscillator();osc.connect(metronomeVolume);metronomeVolume.connect(audioContext.destination);osc.frequency.value=500;if(counter===1){osc.frequency.value=500;}else{osc.frequency.value=300;}osc.start(time);osc.stop(time+0.1);}}
//______________________________________________ENDMetronome
functionplayTick(){console.log("Thisis16thnote:"+counter);counter+=1;futureTickTime+=counterTimeValue;if(counter>16){counter=1;}}
functionscheduler(){if(futureTickTime<audioContext.currentTime+0.1){playMetronome(futureTickTime,true);scheduleSound(kickTrack,kick,counter,futureTickTime-audioContext.currentTime);scheduleSound(snareTrack,snare,counter,futureTickTime-audioContext.currentTime);scheduleSound(hiHatTrack,hihat,counter,futureTickTime-audioContext.currentTime);scheduleSound(shakerTrack,shaker,counter,futureTickTime-audioContext.currentTime);playTick();}
timerID=window.setTimeout(scheduler,0);}scheduler();functionplay(){isPlaying=!isPlaying;
if(isPlaying){counter=1;futureTickTime=audioContext.currentTime;scheduler();}else{window.clearTimeout(timerID);}}$(".play-stop-button").on("click",function(){play();});});
Ifyoulaunchthiscodefromyourserverandclicktheplay/startbutton,itwillstartandstoptheapplication.
CreatingtheUserInterfaceGridSo far you have built a working 16th note sequencer that plays back sound sequences via acollection of “array tracks” in code.You are nowgoing to create a user interface that allowsuserstocreatethesesequencesfromawebpage.
Todo this, you create four div elements positioned as rows, and eachof these contains 16childdivs.TheCSSdisplaysthesechilddivshorizontallyasacollectionofsmallsquares.Thefirstrowcontrolsplaybackofthekickdrum,thesecondrowthesnare,thethirdrowthehi-hat,andthefourthrowtheshaker.Thesequencerhasanadditionalbuttonthatturnsthemetronomeonandoffandaninputsliderthatcontrolsthetempo.
HTMLThefollowingcodeistheHTMLfortheapplication.<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>app</title><scriptsrc="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.js"></script><linkrel="stylesheet"href="css/app.css"><scriptsrc="js/audiolib.js"></script><scriptsrc="js/app.js"></script>
</head><!--_______________________________________________BEGINAPP--><body><divclass="app-grid"></div><buttonclass="play-stop-button">Play/Stop</button><buttonclass="metronome">Togglemetronome</button><divid="tempoBox">Tempo:<spanid="showTempo">120</span>BPM<inputid="tempo"type="range"min="30.0"max="160.0"step="1"value="120"></div></body><!--_______________________________________________ENDAPP--></html>
CSSThefollowingcodeistheCSSfortheapplication.
body{background-color:red;font-size:25px;}
button{margin-bottom:5px;font-size:25px;}
.track-step{width:50px;height:50px;display:inline-block;background-color:orange;outline-style:solid;outline-width:1px;margin-left:5px;}
To create the div elements for the grid, you use a nested JavaScript for loop. Eachcollectionofgriditemshasaparentcontainer.functionplay(){isPlaying=!isPlaying;
if(isPlaying){counter=1;futureTickTime=audioContext.currentTime;scheduler();}else{window.clearTimeout(timerID);}}//__________________________________BEGINcreategridfor(vari=1;i<=4;i+=1){$(".app-grid").append("<divclass='track-"+i+"-container'</div>");for(varj=1;j<17;j+=1){$(“.track-”+i+“-container”).append(“<divclass=’grid-itemtrack-stepstep-"+j+"'</div>");}}//__________________________________ENDcreategrid
Thefollowingcodeallowsyoutotogglethemetronomeonandoff.$(".play-stop-button").on("click",function(){play();});//_________________________________________BEGINmetronometoggle$(".metronome").on("click",function(){if(metronomeVolume.gain.value){metronomeVolume.gain.value=0;}else{metronomeVolume.gain.value=1;}});//__________________________________________ENDmetronometoggle
Nextyouwritecodethat letsuserscontrol the tempofromtheHTMLinputrangeslideranddisplaysthecurrenttempoonthewebpage.First,modifytheplayTick()function:functionplayTick(){secondsPerBeat=60/tempo;counterTimeValue=(secondsPerBeat/4);console.log("Thisis16thnote:"+counter);counter+=1;futureTickTime+=counterTimeValue;if(counter>16){counter=1;}}
Thencreatetheeventlistenerusedtocontrolthetempofromtheslider:$(".metronome").on("click",function(){if(metronomeVolume.gain.value){metronomeVolume.gain.value=0;}else{metronomeVolume.gain.value=1;}});
$("#tempo").on("change",function(){tempo=this.value;$("#showTempo").html(tempo);});
YoucannowmodifythetempoofthesequencebymovingtheHTMLinputslider.
AddingInteractivitytotheGridElementsEachcollectionofelementswithaclassofgrid-itemhasaparent.Theparentelementsaredynamicallycreatedasshowninthefollowingcode:<divclass="track-1-container></div><divclass="track-2-container></div><divclass="track-3-container></div><divclass="track-4-container></div>
JQueryhasamethodnamedindex() that allowsyou to capture an element’s indexvaluerelativetoaparentelement.Inthecaseofthesequencerapplication,theindexvalueofthefirstgrid-itemofeachrowis0andthelastgrid-itemindexis15.Youcangivethisvalueanoffsetof+1sothat thefirst indexgrid-itemisreferencedas1andthelast isreferencedas16.Thisallowsforacorrelationbetweenthegrid-itemindexvaluesandthecountervalue.Youcancapture this informationbysettinganevent listener toallelementswithaclassofgrid-item. When the user clicks the grid-item, the offset index value is either pushed to orremoved fromacorresponding trackarraydependentonwhether thegrid-item isactiveornot.This iswhatdetermines ifasoundwillplayatacertainpoint in themusicsequence.The
followingcodeimplementsthisfeatureandalsomodifiestheCSSbackground-colorofthegrid-itembasedonwhetheritisactiveornot.//__________________________________BEGINcreategridfor(vari=1;i<=4;i+=1){$(".app-grid").append("<divclass='track-"+i+"-container'</div>");for(varj=1;j<17;j+=1){$(".track-"+i+"-container").append("<divclass='grid-itemtrack-stepstep-"+j+"'</div>");}}//__________________________________ENDcreategrid
//______________________BEGINGridinteractivityfunctionsequenceGridToggler(domEle,arr){$(domEle).on("mousedown",".grid-item",function(){
vargridIndexValue=$(this).index();/*__________Getindexofgrid-item*/varoffset=gridIndexValue+1;/*_______________Add+1sovaluestartsat1insteadof0*/varindex=arr.indexOf(offset);/*_______________Checkifvalueexistsinarray*/
if(index>-1){/*______________________________Ifindexofitemexist.....*/
arr.splice(index,1);//_____________________thenremoveit....$(this).css("backgroundColor","");/*________andchangecolorofDOMelementtodefault*/}else{/*_______________________________________Ifitemdoesnotexist.....*/arr.push(offset);/*__________________________thenpushittotrackarray*/$(this).css("background-color","purple");/*_andchangecolorofDOMelementtopurple.*/
}});}
sequenceGridToggler(".track-1-container",kickTrack);sequenceGridToggler(".track-2-container",snareTrack);sequenceGridToggler(".track-3-container",hiHatTrack);sequenceGridToggler(".track-4-container",shakerTrack);//______________________ENDGridinteractivity
Nowsetthetrackarrayssothattheyareempty.varkickTrack=[1,9,11],snareTrack=[5,13],hiHatTrack=[13,14,15,16],shakerTrack=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16];
varkickTrack=[],snareTrack=[],hiHatTrack=[],shakerTrack=[];
Youcannowrunthesequencerandplaybacksoundsbyclickingthesquares.Thetempoalsochangeswhenthesliderismoved.
SummaryInthischapter,youlearnedhowtobuildabasicmusicsequencer.YounowunderstandthecoretechniquesneededtobuildWebAudioAPIapplicationsthatrelyoneventscheduling.
24AJAXandJSON
Inthischapter,youaregoingtolearnhowtoquerydatausingwebAPIsandtocreateyourownweb API for accessing synth patch data to use in a web audio synthesizer. Third-party webservicescommonlyallowaportionoftheirdatatobeaccessibleviaawebAPI.Thisgivesyoutheabilitytoquerydataontheirserverandusetheirdatainyourapplications.AnexampleistheiTunespublicsearchAPIthat letsdeveloperssearchmedia titles in the iTunesstore.Tobegin,youmustfirstlearnabouttwotechnologies:AJAXandJSON.
AJAXAJAXisanacronymthatstandsforAsynchronousJavaScriptandXML.Thisisatechnologythatallows you to use JavaScript to access data asynchronously. You have already worked withAJAX in previous chapterswhen loading audio buffers using theXMLHttpRequest object.The X in AJAX refers to XML, which stands for Extensible Markup Language. This wasoriginally the data exchange format used with AJAX and is rarely used now. Inmodern webdevelopment,thedataexchangeformatyouuseisJSON.
JSONJSONstands forJavaScriptObjectNotation, and it is a data exchange format for transmittingand receiving data over theHTTP protocol whenworkingwithwebAPIs. JSON objects arenearly identical to JavaScript object literals, making them easy to work with. The differencebetweenaJSONobjectandaJavaScriptobjectliteralisthatJSONobjectsarenotassignedtoavariableandtheirkeysneedtobewrittenasstrings.JSONobjectsarestoredinJavaScriptfiles.ThefollowingcodeisanexampleofaJSONobject.{"buzzFunk":[{"type":"sawtooth",
"frequency":65.25
},{"type":"triangle","frequency":65.25
},{
"type":"sawtooth","frequency":67.25
}]}
MakinganAJAXCalltotheiTunesSearchAPITodemonstratehowtointeractwithathird-partywebAPI,youarenowgoingtomakeaquerytotheiTunessearchAPI.
Makeacopyofthe“webaudiotemplate”folderyoucreatedinChapter1,renameitto“itunesapiexample”anddragittothesidebarinSublimeText.Next,referencetheJQuerylibraryfromtheindex.htmlfileandthencopythefollowingcodetotheapp.jsfile.$(function(){varapiURL="https://itunes.apple.com/search?term=funk&media=music&callback=?";$.getJSON(apiURL,function(data){console.log(data);
});});
GotoStartSublimeServerandinyourwebbrowsergotolocalhost:8080.Opentheconsoleandyouwillseeanobjectbeingreturned.
Ifyouclickthearrowandunfoldtheobject,youwillseealistofobjectsthateachcontainsdata.
YouhavejustqueriedtheiTunessearchAPIforanymusicthat includesthekeyword“funk”andarenowinpossessionofaJavaScriptobjectthatcontainsthisdata.
HowtheCodeWorksJQuery has a collection of methods that abstract the XMLHttpRequest object and letsdevelopersmakeAJAX requestswith a simple syntax.Oneof thesemethods is$.getJSON.This method issues a request to a server that returns the queried data. The first argument of$.getJSON is a URL (commonly referred to as an endpoint). The endpoint is written as astring,andifyoulookcloselyyoucanseethesearchtermsembeddedinit.Thesearekey/valuepairssuchasterm=funkandmedia=music."https://itunes.apple.com/search?term=funk&media=music&callback=?";
Thepartoftheendpointafterthe“?”symboliscalledthequerystring.ThispartoftheURLcontainsthedatathatisbeingqueried.The“&”symbolseparatesthekey/valuepairs.
TheiTunesAPIsearchtermsareassignedtospecifickeysandinthepreviouscode,thesearetermandmedia.ThereisnostandardizationacrosswebAPIsforkey/valuenames,andtheyaredifferentforeachwebAPI.BecausetheURLstructureforallwebAPIsisdifferent,youwillneed to read the documentation for any that you areworkingwith. The documentation for theiTunes search API is here: https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/.
ThenextpartoftheURLstringletsyoutosetacallbacktorunoncethequerycompletes.Inthecode example, the callback of$.getJSON is used. If youwant tomake a call to the iTunessearchAPIonpageloadandinvokeafunctiononcompletion,itlookslikethefollowingcode:
HTML<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>app</title><scripttype=”text/javascript”src=”https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.js”></script><scriptsrc=”js/app.js”></script><linkrel=”stylesheet”href=”css/app.css”></head><!--_______________________________________________BEGINAPP--><body></body><!--_______________________________________________ENDAPP--></html>
JavaScriptfunctionlogger(data){console.log(data);}
Inthepreviousexample,afunctionnamedloggerisrunwhenthequerycompletes.The$.getJSONmethodtakesacallbackasasecondargument.Thecallbackreturnsthedata
objectviaanargument.Inthefollowingcode,thisargumentisnameddata,butyoucannameitanythingyouwant.$.getJSON(apiURL,function(data){console.log(data);});
CreatingYourOwnWebAPItoReferenceSynthesizerPatchData
YouarenowgoingtocreateyourownwebAPI.ThegoalofthisexerciseistodemonstratehowtoreferenceaJSONobjectthatcontainssynthesizerpatchdata.Thedatayouwillcreateforyourweb API is a collection of settings for the oscillators of a synth. The user interface of theapplicationappearsasinthefigurebelow,andthecompletedcodeisavailableintheresourceexamplesforthischapter.
Make a new copy of the “web audio template” folder you created in Chapter 1, name thefolder “synthy_api”, and drag the folder to the sidebar in Sublime Text. Next, reference theJQuery library from the index.html file. Inside the “js” folder, create a new file nameddata.jsandcopythefollowingJSONobjecttoitandsavethefile.{"buzzFunk":[{"type":"sawtooth","frequency":65.25
},{
"type":"triangle","frequency":65.25
},{
"type":"sawtooth","frequency":67.25
}]}
Intheapp.jsfile,savethefollowingcode:$(function(){$.getJSON("js/data.js",function(data){console.log(data);});});
Go to Start Sublime Server and in yourweb browser go to localhost:8080. In theChromeconsole,youwillseetheJSONobject.
Ifyouunfoldtheobject,youwillseeitsinternals.
AJSONobjectisdifferentfromaregularJavaScriptobjectliteralbecauseitisnotassignedtoavariable.However, once thedata is returned, it is assigned to avariable and canbepassedaroundandassignedtoothervariables.$(function(){$.getJSON(“js/data.js”,function(data){varpatchParams=data;console.log(patchParams);//object});});
TheDataStructureThe data structure of the JSONobject you areworkingwith contains a single object propertynamedbuzzFunk,which is an array containing three objects and each holds oscillator data.Whenthisdataisloadedintoyoursynth,all threeoscillatorscombinetocreateasinglesound.Eachobjecthastypeandfrequencysettingsfortheoscillatorthatreferencesit.
TheHTML andCSS codes for the keyboard interface used to play the synth that loads theJSON data are given below. Copy the HTML code to index.html and the CSS code toapp.css.
HTML<body><h1>SynthyAPI</h1><ulid="piano">
<li><divclass="white-keykey"id="c1"></div></li><li><divclass="black-keykey"id="c#1"></div></li><li><divclass="white-keykey"id="d1"></div></li><li><divclass="black-keykey"id="d#1"></div></li><li><divclass="white-keykey"id="e1"></div></li><li><divclass="white-keykey"id="f1"></div></li><li><divclass="black-keykey"id="f#1"></div></li><li><divclass="white-keykey"id="g1"></div></li><li><divclass="black-keykey"id="g#1"></div></li><li><divclass="white-keykey"id="a1"></div></li><li><divclass="black-keykey"id="b#1"></div></li><li><divclass="white-keykey"id="b1"></div></li><li><divclass="white-keykey"id="c2"></div></li><li><divclass="black-keykey"id="c#2"></div></li><li><divclass="white-keykey"id="d2"></div></li><li><divclass="black-keykey"id="d#2"></div></li><li><divclass="white-keykey"id="e2"></div></li><li><divclass="white-keykey"id="f2"></div></li><li><divclass="black-keykey"id="f#2"></div></li><li><divclass="white-keykey"id="g2"></div></li><li><divclass="black-keykey"id="g#2"></div></li><li><divclass="white-keykey"id="a2"></div></li><li><divclass="black-keykey"id="b#2"></div></li><li><divclass="white-keykey"id="b2"></div></li><li><divclass="white-keykey"id="c3"></div></li><li><divclass="black-keykey"id="c#3"></div></li><li><divclass="white-keykey"id="d3"></div></li><li><divclass="black-keykey"id="d#3"></div></li><li><divclass="white-keykey"id="e3"></div></li><li><divclass="white-keykey"id="f3"></div></li><li><divclass="black-keykey"id="f#3"></div></li><li><divclass="white-keykey"id="g3"></div></li><li><divclass="black-keykey"id="g#3"></div></li><li><divclass="white-keykey"id="a3"></div></li><li><divclass="black-keykey"id="b#3"></div></li><li><divclass="white-keykey"id="b3"></div></li></ul>
</body>
CSSbody{background-color:purple;}
h1{font-family:"impact";color:rgb(228,208,230);margin-left:10%;font-size:70px;}
li{list-style:none;float:left;display:inline;width:40px;position:relative;}
.white-key{display:block;height:220px;background:#fff;border:1pxsolid#ddd;border-radius:003px3px;}
.black-key{display:inline-block;position:absolute;top:0px;left:-12px;width:25px;height:125px;background:#000;z-index:1;
}
The application uses a factory function to load the JSON data. This function takes twoarguments. The first argument is an endpoint that contains the JSON file, and the second is apropertyoftheJSONobjectthatcontainsthepatchyouwanttoload.Currently,theJSONfilehasonlyonepatchnamedbuzzFunk.ThefinalloadinginterfacefortheJSONdatalookslikethefollowingcode:varsynth=apiReader("js/data.js","buzzFunk");//loadpatchsynth.play(keyByDOMIndex);//playaspecificnoteonkeyboardsynth.stop();//stopplaying
Deleteanycodepresentinapp.jsandreplaceitwiththefollowingcode:
"usestrict";varsynth=apiReader("js/data.js","buzzFunk");$(function(){$(".key").on("mouseover",function(){varindex=$(this).index('.key');synth.play(index);});$(".key").on("mouseout",function(){synth.stop();});});
Inthe“js”folder,createanewfilenamedmodule.jsandreferenceitintheindex.htmlfilebetweentheJQuerylibraryandapp.jsfile.
<head><metacharset="UTF-8"><title>app</title><scripttype="text/javascript"src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.js"charset="utf-8"></script><scriptsrc="js/module.js"></script><scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/app.css"></head>
Inmodule.js,copyandsavethefollowingcode:"usestrict";varaudioContext=newAudioContext();
varapiReader=function(endpoint,patchProp){
$(function(){
$.getJSON(endpoint,function(data){app.patchParams=data[patchProp];})
});
varapp={
patchParams:undefined,oscillators:undefined,
play:function(id){
app.oscillators=app.patchParams.map(function(val){
varosc=audioContext.createOscillator();osc.type=val.type;osc.frequency.value=val.frequency;osc.detune.value=(val.frequency)+(id*100);osc.connect(audioContext.destination)osc.start(audioContext.currentTime)
returnosc;});
},stop:function(){for(vari=0;i<app.oscillators.length;i+=1){app.oscillators[i].stop(audioContext.currentTime);
}}}
returnapp;
};
Launchtheindex.htmlfilefromSublimeServerandhoveryourmouseoverthesynthkeys.Youwillhear thesynthplayacollectionofoscillators that reference thesettings in the loadedpatchdata.
HowtheCodeWorksInthemodule.jsfile,thefactoryfunctionnamedapiReadertakestwoarguments.Thefirstargument is named endpoint and is the endpoint location of the JSON file. The secondargument isnamedpatchPropand is thepropertyof theJSONobject thatcontains thesynthpatchdata.Theendpointargumentvalueispassedtothe$.getJSONmethod.Inthebodyofthe$.getJSONcallback, thedesiredpatchof thereturnedobject is referencedandstored inapropertyoftheappobjectnamedapp.patchParams.
Thepurposeoftheappobjectistocontainthepropertiesandmethodsthatcreate,connect,start,andstoposcillatorsusingthesettingsthatarelistedintheJSONobjectproperty.Thefirstmethodoftheappobjectisnamedplay.It takesasingleargumentandistheindexvalueofaDOMelement that representsakey.When theplaymethod is invoked, themapmethod loopsthrougheachobjectintheapp.patchParamsarrayandcreatesanoscillatoroneachiteration.The type, frequency.value, and detune.value properties of each oscillator areassigned.Theoscillatoristhenconnectedtothenodegraphandsettostartplaying.app.oscillators=app.patchParams.map(function(val){varosc=audioContext.createOscillator();osc.type=val.type;osc.frequency.value=val.frequency;osc.detune.value=(val.frequency)+(id*100);osc.connect(audioContext.destination);osc.start(audioContext.currentTime);returnosc;});
ThefollowingcodeprovidestheindexvalueofaDOMelement(thekeyboardnotetheuserhoverstheirmouseover)multipliedby100.Theresultisaddedtotheoscillatorfrequencyandassigned to the detune.value property. This makes the oscillators play back at half-stepintervalsrelativetothekeyboardinterface.osc.detune.value=(val.frequency)+(id*100);
Eachoscillatoristhenreturnedandstoredinanarraynamedapp.oscillators.Thestop method is used to stop the oscillators from playing. Thismethod loops through
app.oscillatorsandinvokesaWebAudioAPIstopmethodoneachone.stop:function(){for(vari=0;i<app.oscillators.length;i+=1){app.oscillators[i].stop(audioContext.currentTime);}}
Inapp.js,theapiReaderfunctionisinvoked,whichreturnsanobjectnamedsynth.varsynth=apiReader("js/data.js","buzzFunk");
Theplayandstopmethodsareplacedintwoeventlistenerstostartandstoptheoscillatorsonmouseevents.$(".key").on("mouseover",function(){varindex=$(this).index('.key');synth.play(index);});$(".key").on("mouseout",function(){synth.stop();});
Whentheplaymethodisinvoked,thecurrentindexvalueofthedivelement(the“keyboardnote”)iscapturedandpassedtothefunction.varindex=$(this).index('.key');//__getindexvalueofkeysynth.play(index);//__________________passittoplaymethod
BuildingontheAPIThecodeinmodule.jsisdesignedtoloadonlythetypeandfrequencyofoscillators,butwhat if youwanted to load other custom settings such as volume?The following code adds avolumesettingtoeachoscillator.
data.js{"buzzFunk":[{"type":"sawtooth","frequency":65.25,"volume":1},{"type":"triangle","frequency":65.25,"volume":1},{"type":"sawtooth","frequency":67.25,
"volume":0.3}]}
module.js"usestrict";varaudioContext=newAudioContext();varapiReader=function(endpoint,patchProp){
$(function(){
$.getJSON(endpoint,function(data){app.patchParams=data[patchProp];})
});varapp={patchParams:undefined,gainNodes:undefined,oscillators:undefined,
play:function(id){
app.gainNodes=app.patchParams.map(function(val){
vargain=audioContext.createGain();gain.gain.value=val.volume;returngain;
});app.oscillators=app.patchParams.map(function(val,i){
varosc=audioContext.createOscillator();osc.type=val.type;osc.frequency.value=val.frequency;osc.detune.value=(val.frequency)+(id*100);osc.connect(app.gainNodes[i]);app.gainNodes[i].connect(audioContext.destination);osc.connect(audioContext.destination);osc.start(audioContext.currentTime);
returnosc;});},stop:function(){for(vari=0;i<app.oscillators.length;i+=1){app.oscillators[i].stop(audioContext.currentTime);}}}returnapp};
Thesefilemodificationsgiveyourcodetheability tocreateagainnodeforeachoscillator.Theplaymethodoftheappobjectcontainsamapmethodthatcreatesthegainnodesandsets
thegain.gain.value property of each one to the value of the current object’s volumeproperty.Allgainnodesareplacedinanarraythatisassignedtoapp.gainNodes.app.gainNodes=app.patchParams.map(function(val){vargain=audioContext.createGain();gain.gain.value=val.volume;returngain;});
Theoscillatorsarethenconnectedtothegainnodesinthesecondmapmethod.app.oscillators=app.patchParams.map(function(val,i){
varosc=audioContext.createOscillator();osc.type=val.type;osc.frequency.value=val.frequency;osc.detune.value=(val.frequency)+(id*100);osc.connect(app.gainNodes[i]);app.gainNodes[i].connect(audioContext.destination);osc.start(audioContext.currentTime);
returnosc;});
ExtendtheJSONObjectTheJSONobjecthasonlyone“patch.”Youcanextenditwithasmanypatchesasyoulike.Thefollowingcodeextendstheobjectwithaproperty(patch)namedgameSound.{"buzzFunk":[{"type":"sawtooth","frequency":65.25,"volume":1},{"type":"triangle","frequency":65.25,"volume":1},{
"type":"sawtooth","frequency":67.25,"volume":0.3}],"gameSound":[{"type":"square","frequency":100.25,"volume":1},{"type":"triangle","frequency":65.25,"volume":1},{
"type":"sawtooth",
"frequency":67.25,"volume":0.3}]}
YouthenaccessthegameSoundsettingsbyloadingthemwithapiReader.
varsynth=apiReader("js/data.js","gameSound");
SummaryInthischapter,youlearnedhowtoquerythird-partywebAPIs,workwithJSONfiles,andcreateyourownwebAPItoloadpatchdataforasynthesizer.Theapplicationyoucreatedonlybeginstoexplore what is possible. For a challenge, try incorporating filters, LFOs, delays, and othersettings.Inthenextchapter,youwill learnaboutthefutureofJavaScriptandvariousresourcesforcontinuedlearning.
25TheFutureofJavaScriptandtheWebAudioAPI
Inthisbook,youhavelearnedthecoreconceptsbehindtheJavaScriptprogramminglanguageandtheWebAudioAPI.To keep fromovercomplicating things, some parts of both the JavaScriptlanguageandtheWebAudioAPIhavebeenomitted.Thischapterpresentssomeoftheareasthatwereskippedandprovidesyouafewsuggestionsaboutwhatyoucanlearnnowtofuture-proofyournewskills.
TheWebAudioAPI1.0Asof thiswriting, theWebAudioAPIhasnot reachedversion1.0.Thismeans that there areparts of the API that are either changing or have changed but are not implemented in webbrowsers.Thefollowingtwosectionstalkmoreaboutthis.
3DSpacialPositioningInadditiontotheStereoPannernode,therearetwootherspacialpositioningnodes.Bothofthese nodes allow for 3D style panning. One is called Panner and the other is calledSpacialPanner.Panner has been recently deprecated. The replacement for Panner isSpacialPanner.Asofthiswriting,SpacialPannerhasnotbeenimplementedinanywebbrowsers.Thismakesitdifficulttowriteaboutitandchecktheaccuracyofcodesamples.Andfor this reason, we opted to omit a detailed explanation ofSpacialPanner and present ageneralsummaryhere.
Theideabehind3Dspacialpositioningisthatsoundismodifiedinrelationtotwoobjectsinathree-dimensionalspace.Thefirstobjectisasoundsourcethathasitsspacialpositioningmovedusing SpatialPanner. The other object is called SpatialListener that represents areal-worldhuman listener.Theutilityof thisapproach is that theSpatialListener object
canbeprogrammedtoworkwithanavatarsuchasavideogamecharacter,wheresoundthatisgeneratedina“virtualworld”isperceivedfromafirst-personperspective.Volumechangestakeplaceautomaticallybasedonthevirtual“distance”betweentheSpatialListenerandanysound-generatingvirtualobjects.Foraddedrealism,filters,reverberation,andothereffectscanbeprogrammedtochangethecharacteristicsofsoundbasedontheperceivedpositionofvirtualobjects. You can read about SpatialPanner at the following URL:https://www.w3.org/TR/webaudio/#the-spatialpannernode-interface.
You can read about the SpatialListener at this URL:https://www.w3.org/TR/webaudio/#idl-def-SpatialListener.
RawModificationofAudioBufferDataTheWebAudioAPIallowsfortherawmodificationofaudiodata.Youdothisbyeithercreatingemptyaudiobuffersandpopulatingthemwithyourownprogrammeddataorbymodifyingbuffersthatalreadycontaindatasuchasaudiofileinformation.Thesemodificationscanbeusedtocreatecustomeffectsandotherusefulthingslikenoisegenerators.Thenodeinitiallyusedforthiswasnamed ScriptProcessor, but this been deprecated and replaced with a node namedAudioWorker. Unfortunately, as of this writing there are no web browsers that haveimplementedtheAudioWorkernode,soadetailedexplorationofithasbeenomittedfromthisbook. You can read about the AudioWorker node at this URL:https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API#Audio_Workers.
SuggestionsforContinuedLearningJavaScript6http://es6-features.org/(unofficial)
JavaScript6,technicallycalledECMAScript6orcommonlyreferredtoasES6,isthenewestversion of the JavaScript language and is currently being implemented in various JavaScriptenvironments. The material in this book is focused on the ECMAScript 5 standard and isreflective of the majority of JavaScript in use around the world at the time of this writing. Isuggest that you learn ES6 moving forward. ES6 has unique features such as block-scopedvariables that build on the ES5 specification. Everything you have learned about ES5 istransferabletoES6.
node.jshttps://nodejs.org
Node.jsisaserver-sideJavaScriptenvironmentbasedonV8,whichisthesameJavaScriptenginethatrunsGoogleChrome.InsteadofrunningJavaScriptfromawebbrowser,Node.jsallows you to run JavaScript from the terminal on your computer. It can be used to automatecomputertasks,runwebservers,andcommunicatewithdatabases.
TheWebMIDIAPIhttps://www.w3.org/TR/webmidi/
MIDI, which stands for Music Instrument Digital Interface, is a digital music instrumentprotocol created in 1982 byDaveSmith andChetWood.TheWebMIDIAPI allows users tocontrolandmanipulateMIDI-equippeddevicesusingwebbrowsers.
OpenSoundControlhttp://opensoundcontrol.org/
According to their website, Open Sound Control (OSC) is “a protocol for communicationamongcomputers,soundsynthesizers,andothermultimediadevicesthatisoptimizedformodernnetworking technology.” In other words, OSC is a protocol that facilitates the communicationbetweenhardwareandsoftwareoveranetwork.
SummaryIn this chapter, youwere presentedwith a list of options for continued learning. Even thoughJavaScripthasbeen taughthere in thecontextofworkingwithaudio, it is important tokeep inmindthatprogrammingisausefulcross-disciplinaryskillthatyoucanusetosolvemanydifferenttypesofproblems.
FurtherReading■JavaScript:TheDefinitiveGuidebyDavidFlanagan.
■UnderstandingECMAScript6byNicholasC.Zakas.
■ Programming JavaScriptApplications:RobustWebArchitecturewithNode,HTML5, andModernJSLibrariesbyEricElliott.
■YouDon’tKnowJSBookSeriesbyKyleSimpson.
■Node.jstheRightWay:Practical,Server-SideJavaScriptThatScalesbyJimR.Wilson.
■WebAudioAPIbyBorisSmus.
BookWebsitehttp://www.javascriptforsoundartists.com
Index
3Dspacialpositioning,221–222
AAbstraction,129–135,185–189Additionassignment,24–25AJAX,3,207–209Algebrarules,18Ambience,151Analysernode,175–183ANDoperator,29Animation,107,182Anonymousfunctions,48–49;seealsoFunctionsAPI(applicationprogramminginterface),3–4;seealsoWebAudioAPIAppinterface,modifying,81–84Application,building,71–89,93–102Application,refactoring,108–110Arguments,16,43–44Arithmeticoperators,18,23–25;seealsoNumbersArrays
callbackfunctionsand,53–54forloopsand,36–37frequencydataarrays,180–182numbersand,21–22objectsand,60trackarrays,197–206
Assignmentoperators,11,23–25;seealsoOperatorsAsynchronouscodeexecution,118–120
AsynchronousJavaScriptandXML(AJAX),3,207–209Attackproperty,166AudioBatchLoaderfunction,130–131,134–135,185–189AudioContext()method,65AudioFileLoaderfunction,131–134,186–192,198–200Audiofiles
abstracting,129–135asynchronouscodeexecution,118–120buffer,116–120,222compatibilityissues,118decoding,116,119getrequests,116–120importing,117–120impulseresponsefiles,151–154loading,115–120,129–135,185–192,198–200modifying,222playing,10,115–120,129–135prerequisitesfor,115–116synchronouscodeexecution,118–120XMLHttpRequest,116–120
Audioinputsources,137–142,155–167,175–179,183–185Audioloaderabstraction,185–189Audioloaderlibrary
creating,121,185flexibilityfor,185–189modifying,186–189updating,171,183,185
Audioparameters,140–141,171–174Audiovisualizations,creating,175–183
BBackgroundcolor,78–82,86–87,178,203–206,214Bangoperator,28Binary-codeddecimalnumbers,176Binarysystem,176Bincount,177–182Bindfunction,62–64Bins,179–182Biquadfilternode
foraudiofiltering,140–142fordesigningequalizers,144–149typesof,144–147using,143–144
Bits,176Block-levelelement,75–76,84,87–89Booleandatatype,25–26Booleanvalues,25–29Border,84–85Boundobject,62–64Boxmodel,74Browsersetupview,6Bufferdata,modifying,222Buffer,processing,116–120Buffersourcenode,119–120Bulletpoints,86Bytes,176
CCallbackfunctions,52–54Caseinsensitivity,16CDN(contentdeliverynetwork),103–105Centeringelements,87–89Changeability,13Channelmergernodes,159–160Channelmerging,158–160Channelsplitting,158–160CharAt(),15–17Childselectors,80Chromedevelopertools,7–8,10,14,78Classes,121–122Classidentifier,81Cloningobjects,60Closures,49–52Code,10–12Codeabstraction,129–135Codeblock,36,79Codesnippets,6–7Colorselection,78–82,86–87,178,203–206,214
Comments,12,72,79Comparisonoperators,26–30;seealsoOperatorsCompatibilityissues,118Concat(),21–22Concatenation,14–15,20–22Conditionalstatements,31–35Console.log(),13–14Constructors,121,125–128Convolvernode,140,151–155Convolverreverb,151–155CSS(cascadingstylesheets)
appinterface,81–84block-levelelements,87–89border,84–85forbuildinguserinterface,77–89bulletpoints,86buttons,74–79checkingerrorsin,77–78childselectors,80codeblock,79colorselection,78–82,86–87,178,203–206,214comments,79datastructureof,214–216descendentselectors,80DOMprogramming,91–93,102elementselectors,79explanationof,2–3,74,77–78inlineelement,84withJQuery,106–108linkelement,77listelement,86margins,84–85,87–89methodchaining,107–108padding,84–85selectingelements,105sequencer,203–204sliders,74,76–78spectrumanalyzer,178
DData.js,211–217,220Data,private,124–125,127Data,query,207–220Datatypes,11,57–58Decimalnumbers,19,169,176,180Delaynode,140,161–164Deletekeyword,188Descendentselectors,80Destination,9–10Detuneproperty,69Developertools,7–8,10,14,78Displayinterface,building,181–182Divelement,73,75–76Divisionassignment,25DocumentObjectModel(DOM),74DOMprogramming
APImethods,92buildingapplication,93–102CSSand,91–93,102eventlistener,99–100explanationof,74frequencyslider,96–99frequencyspectrumanalyzer,181–183HTMLand,74,91–102withJavaScript,74,91–102,181withJQuery,103–113nodes,96,100selectors,105–106,111–112,153setIntervalmethod,97–101simplifying,103–113start/stoptext,94–96triggeringoscillator,93–94
Drumsequence,191–206;seealsoSequencerDynamiccompressornode,165–167Dynamicobjectextension,123–124;seealsoObjectsDynamicrangecompression,140,165–167
E
Echoeffects,162Effects
buildingblocksfor,141–142echoeffects,162effectsbox,42–43exampleof,141nodesfor,139–142ping-pongeffects,163–164slapbackeffects,162–163typesof,139–142workingwith,137–142
Elementsblock-levelelements,75–76,84,87–89centering,87–89changing,106–107divelement,73,75–76formelement,73–74,76–78headingelement,73horizontalruleelement,73,76inlineelement,75–76,84inputelement,73–74,76–78,107–108,110linkelement,77listelement,73,86listitemelement,73,86paragraphelement,73parentelement,80,86–87,205selecting,79,105spanelement,73,75–76storing,105–106unorderedlistelement,73,81
Elementselectors,79,105Equalityoperator,26–27Equalizers,137,140,142–149Equaltooperator,27–28Eventlistener
audiobuffer,120DOMprogramming,99–100JQuery,107–111playback,135,217tempochanges,204–205
ExponentialRampToValueAtTimemethod,172
FFactories,121–128FastFouriertransforms(FFTs),176,179–182Fileloader,115–120,129–135Filetypecompatibility,118Filter(),53Filtertypes,144–147Fontcolor,86–87Fontsize,86–87Fonttype,86–87Forinloop,59–60Forloop,35–37Formelement,73–74,76–78Fourieranalysis,175–176Frequency,changing,97–99FrequencyDataarray,180–182Frequencydata,storing,180–181Frequencyproperty,69Frequencyslider,96–99Frequencyspectrumanalyzer,176–183Functions
anonymousfunctions,48–49argumentsobject,43–44bindfunction,62–64callbackfunctions,52–54closures,49–52declaringvariables,46–50effectsbox,42–43exampleof,39–44explanationof,39expressionsfor,41hoisting,46–48oscillatorplayback,41–43partsof,40–41recursion,54–55scopeconcept,44–47
GGainnodes,138–139,194Getrequests,116–120Getters,124–125Globalobject,63–64Globalreplace,16Globalscope,44–47,64Globalvariables,46–50,119GoogleChrome,4–7;seealsoChromedevelopertoolsGraphicequalizer,143,146–148;seealsoEqualizersGreaterthanoperator,27–28Groupingselectors,80
HHeadingelement,73“HelloSound”application,9–11Hoistingvariables,46–48Horizontalruleelement,73,76HTML(hypertextmarkuplanguage)
block-levelelements,75–76forbuildinguserinterface,71–77checkingerrorsin,72comments,72datastructure,213DOMprogramming,74,91–102elements,2,71–74explanationof,2–3,71–74formelement,76–78impulseresponsefiles,153inlineelement,75–76inputelement,76–78iTunesAPI,210JQueryand,105–108methodchaining,107–108selectingelements,105sequencer,202–203spectrumanalyzer,177–178tags,2,71–74templates,72–73
thiskeyword,108treestructure,74–75typeattribute,76–77,107valueattribute,77
IIdentifiers,81ididentifier,81Ifstatement,32–33Immutability,13Impulseresponsefiles,151–154Inlineelement,75–76,84Inoperator,60Inputelement,73–74,76–78,107–108,110Inputsources,137–142,155–167,175–179,183–185iTunesAPI,208–210
JJavaScript
buildingapplication,93–102changingproperties,106–107classesand,121–122datatypes,11,57–58DOMprogramming,74,91–102,181explanationof,1–2futureof,221–223gettingstartedwith,9–22impulseresponsefiles,151–154methodchaining,107–108newkeyword,117,125–126object-orientedprogramming,121–122overviewof,1–8setupviewinbrowser,4–6spectrumanalyzer,176–177strictmodefor,5,63–64,93thiskeyword,61–62,108,126
JavaScript6,222JavaScriptObjectNotation,208;seealsoJSONobject
JQueryCDNand,103–105changingproperties,106–107CSSand,106–109DOMprogramming,103–113eventlistener,107–111explanationof,103HTMLand,105–108methodchaining,107–108methods,106onOffin,112oscillatorwith,108–109refactoringapplication,108–110referencing,104selectingelements,105setIntervalin,111–112setup,103–104spectrumanalyzer,176–177storingelements,105–106thiskeyword,108using,105–113
JSONobjectdata.js,211–217,220datastructure,213–216explanationof,207–208extending,219–220iTunesAPI,208–210module.js,215–219patches,210–212,216,219–220playback,216–217forwebAPI,210–217
KKneeproperty,166
LLengthproperty,15,17Lessthanoperator,27–28
LinearRampToValueAtTimemethod,173Linkelement,77Listbulletpoints,86Listelement,73,86Listitemelement,73,86Localscope,44–47LogicalANDoperator,29LogicalNOToperator,29–30Logicaloperators,28–30LogicalORoperator,29Loops
explanationof,31–32forloop,35–37forinloop,59–60loopingsounds,170–171loopproperty,170–171whileloop,37–38
Low-passfilter,141
MMacsetupview,6Map(),53–54Margins,84–85,87–89Math.abs(),20Math.ceil(),19Math.floor(),19Math.max(),19Mathmethods,18–20Math.min(),19Mathobject,18Math.random(),19–20Mergernodes,155,158–160Methodchaining,107–108Metronomefunction,194–205Mixingchannels,138Modificationnodes,139Module.js,215–219Moduloassignment,25
Monochannels,159–160Multibandequalizer,140,142;seealsoEqualizersMultichannelfiles,157–160Multiplicationassignment,25Musicsequencer,191–206;seealsoSequencerMutability,13
NNewkeyword,117,125–126Nodegraphs;seealsoNodes
buffernodesource,119–120effectsnodes,139–142exampleof,141explanationof,137–138gainnodes,138–139inputsources,138modificationnodes,139forWebAudioAPI,65–66,137–142
Node.js,223Nodes
analysernode,175–183biquadfilternode,140–149buffersourcenode,119–120channelmergernodes,159–160channelsplitting,158–160convolvernode,140,151–155delaynode,140,161–164dynamiccompressornode,140,165–167effectsnodes,139–142explanationof,138–139futureof,221–223gainnodes,138–139,194mergernodes,155,158–160modificationnodes,139nodegraphs,65–66,137–142placementof,138–139splitternodes,158–160stereopannernode,140,157–158forWebAudioAPI,65–66,137–142
Notequaltooperator,28NOToperator,29–30Null,12Numbers
algebrarules,18arithmeticoperators,18,23–25arraysand,21–22decimals,19,169,176,180mathmethods,18–20mathobject,18precedentexample,18–20precedentrules,18
Number-to-stringconversion,20
OObjectliterals,57–58,117,208,212Object-orientedprogramming,121–122Objects
arraysand,60boundobject,62–64classesand,122cloning,60–61datatypes,57–64dynamicobjectextension,123–124factoriesand,122–124globalobject,63–64literals,57–58,117,208,212loopingthrough,59–60methodfor,60programming,121–122propertyfor,60prototypalinheritance,61–62prototypeobject,126–128
Onendedproperty,67OnOffselector,93–101,112OpenSoundControl(OSC),223Operand,23Operators
additionassignment,24–25
arithmeticoperators,18,23–25assignmentoperators,11,23–25bangoperator,28categoriesof,23–25comparisonoperators,26–30divisionassignment,25equalityoperator,26–27equaltooperator,27–28explanationof,23–24greaterthanoperator,27–28inoperator,60lessthanoperator,27–28logicaloperators,28–30moduloassignment,25multiplicationassignment,25notequaltooperator,28operand,23setIntervalmethod,24–25strictequalityoperator,27strictnotequaltooperator,28subtractionassignment,25
ORoperator,29Oscillators
creating,41–45,66–69,119–120detuneproperty,69frequencyproperty,69withJQuery,108–109methodsfor,66–67onendedproperty,67playback,9–10,32,41–43,66–67,93–94,108–109propertiesof,66–67refactoringapplication,108–110restarting,67–68start/stoptext,94–96stopmethod,67–68triggering,93–94typeproperty,68–69forvariables,12–13forWebAudioAPI,9–10,66–69
PPadding,84–85Paragraphelement,73Parametricequalizers,143,146–149Parentelement,80,86–87,205Ping-pongdelay,163–164Playback
audiofiles,10,115–120,129–135audioparameters,171–174convolverreverb,153–154eventlistener,135,217explanationof,4forJSONobject,216–217loopingsounds,170–171oscillatorplayback,9–10,32,41–43,66–67,93–94,108–109forsequencer,192,197–202
Pop(),21–22Precedentexample,18–20Primitivedatatypes,57Privatedata,124–125,127Property
attackproperty,166changing,106–107detuneproperty,69explanationof,17frequencyproperty,69kneeproperty,166lengthproperty,15,17loopproperty,170–171onendedproperty,67prototypeproperty,126–128ratioproperty,166reductionproperty,165–167releaseproperty,166thresholdproperty,166typeproperty,68–69
Prototypalinheritance,61–62Prototypeobject,126–128Prototypeproperty,126–128
Push(),21
QQuerystring,209–210
RRatioproperty,166Recursion,54–55Reductionproperty,165–167Regularexpressions,16Releaseproperty,166Replace(),16Resources,8,15,151–152,222–223Reverb,151–155
SScopeconcept,44–47Selectors
childselectors,80descendentselectors,80DOMselectors,105–106,111–112,153elementselectors,79,105grouping,80storing,105–106
Sequencerbuilding,191–206CSSfor,203–204gridelements,202–206HTMLfor,202–203interactivityfor,205–206metronome,194–205playback,192,197–202rhythmicpatterns,192–193tempochanges,192,195–206timingclock,193–204trackarrays,197–206userinterfacegrid,202–206
SetIntervalmethodassignmentoperators,24–25DOMprogramming,97–101JQuery,111–112rhythmicpatterns,192–193
SetTargetAtTime()method,173Setters,124–125SetTimeoutmethod,192–197,199–201SetValueAtTimemethod,172SetValueCurveAtTime()method,173–174Shift(),21–22Single-bandequalizer,143,148;seealsoEqualizersSinglemonochannels,159–160Slapbackeffects,162–163Slice(),16–17Spacialplannernode,221–222Spanelement,73,75–76Spatiallistenernode,221–222Speakers,138Spectrumanalyzer,connecting,181–183Spectrumanalyzer,creating,176–183Splitternodes,158–160Startmethod,170–171Stepsequencer,191–206;seealsoSequencerStereopannernode,140,157–158,221–222Stopmethod,67–68Strictequalityoperator,27Strictmode,5,63–64,93Strictnotequaltooperator,28Strictstring,93–97,100,109Stringdatatypes,11,14–16Stringmethods,15–17Strings
caseinsensitivity,16datatypes,11,14–16globalreplace,16lengthproperty,15,17manipulating,14–17methods,15–17number-to-stringconversion,20
regularexpressions,16resourcesfor,15valuesfor,16–17variablesand,14–17
SublimeText,4–6,115Subtractionassignment,25Switchstatement,33–34Synchronouscodeexecution,118–120Synthesizerpatchdata,210–212,216,219–220
TTemplatefolders,4–5Templates,72–73Tempo,changing,192,195–206Ternarystatement,34–35Textsize,86–87Third-partywebAPIs,207–220Thiskeyword,61–62,108,126Thresholdproperty,166Time
audioparametermethods,171–174loopingsounds,170–171startmethod,170–171timingclock,169–170,193–204workingwith,169–174
ToLowerCase(),15ToUpperCase(),15Trackarrays,197–206Troubleshootingtips,8–9Typeattribute,76–77Typeproperty,68–69Typestyle,86–87
UUnorderedlistelement,73,81Unshift(),21–22Userinterface(UI)
building,71–89
CSSfor,77–89explanationof,71–72HTMLfor,71–77modifying,81–84
VValueattribute,77,106,108,110Values
assigning,11,13,23–26Booleanvalues,25–29forstrings,16–17
Variablesassigningvaluesto,11,13,23–26assignments,10–14changeabilityof,13concatenationof,14–15creating,10datatypeof,17–18declaring,46–50globalvariables,46–50,119hoisting,46–48immutabilityof,13mutabilityof,13names,13–14nullvalue,12oscillatorfor,12–13overwriting,13stringsand,14–17undefinedvalue,12understanding,10–11waveforms,10–14,17–18,21
Visualizations,creating,175–183Volumecontrol,138
WW3C(WorldWideWebConsortium),72,77Waveforms
changing,99–100outlinefor,101–102
selecting,99–102typesof,10–14,41–44,68–69,99–102variables,10–14,17–18,21
WebAudioAPIaudiooutput,9–10creating,210–212destination,9–10explanationof,3–4featuresof,65–66futureof,221–223gettingstartedwith,9–22“HelloSound”application,9–11nodesfor,65–66,137–142oscillators,9–10,66–69resourcesfor,8variables,10–11waveforms,11–14,17–18,21
WebMIDIAPI,223Whileloop,37–38Windowssetupview,6Workenvironment,4–6
XXML(ExtensibleMarkupLanguage),207;seealsoAJAXXMLHttpRequest,116–120,131–133,152–153,207–209
ZZero-basedindex,16,43