123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336 |
- /*! p5.sound.js v0.3.2 2016-11-01 */
- (function (root, factory) {
- if (typeof define === 'function' && define.amd)
- define('p5.sound', ['p5'], function (p5) { (factory(p5));});
- else if (typeof exports === 'object')
- factory(require('../p5'));
- else
- factory(root['p5']);
- }(this, function (p5) {
- /**
- * p5.sound extends p5 with <a href="http://caniuse.com/audio-api"
- * target="_blank">Web Audio</a> functionality including audio input,
- * playback, analysis and synthesis.
- * <br/><br/>
- * <a href="#/p5.SoundFile"><b>p5.SoundFile</b></a>: Load and play sound files.<br/>
- * <a href="#/p5.Amplitude"><b>p5.Amplitude</b></a>: Get the current volume of a sound.<br/>
- * <a href="#/p5.AudioIn"><b>p5.AudioIn</b></a>: Get sound from an input source, typically
- * a computer microphone.<br/>
- * <a href="#/p5.FFT"><b>p5.FFT</b></a>: Analyze the frequency of sound. Returns
- * results from the frequency spectrum or time domain (waveform).<br/>
- * <a href="#/p5.Oscillator"><b>p5.Oscillator</b></a>: Generate Sine,
- * Triangle, Square and Sawtooth waveforms. Base class of
- * <a href="#/p5.Noise">p5.Noise</a> and <a href="#/p5.Pulse">p5.Pulse</a>.
- * <br/>
- * <a href="#/p5.Env"><b>p5.Env</b></a>: An Envelope is a series
- * of fades over time. Often used to control an object's
- * output gain level as an "ADSR Envelope" (Attack, Decay,
- * Sustain, Release). Can also modulate other parameters.<br/>
- * <a href="#/p5.Delay"><b>p5.Delay</b></a>: A delay effect with
- * parameters for feedback, delayTime, and lowpass filter.<br/>
- * <a href="#/p5.Filter"><b>p5.Filter</b></a>: Filter the frequency range of a
- * sound.
- * <br/>
- * <a href="#/p5.Reverb"><b>p5.Reverb</b></a>: Add reverb to a sound by specifying
- * duration and decay. <br/>
- * <b><a href="#/p5.Convolver">p5.Convolver</a>:</b> Extends
- * <a href="#/p5.Reverb">p5.Reverb</a> to simulate the sound of real
- * physical spaces through convolution.<br/>
- * <b><a href="#/p5.SoundRecorder">p5.SoundRecorder</a></b>: Record sound for playback
- * / save the .wav file.
- * <b><a href="#/p5.Phrase">p5.Phrase</a></b>, <b><a href="#/p5.Part">p5.Part</a></b> and
- * <b><a href="#/p5.Score">p5.Score</a></b>: Compose musical sequences.
- * <br/><br/>
- * p5.sound is on <a href="https://github.com/therewasaguy/p5.sound/">GitHub</a>.
- * Download the latest version
- * <a href="https://github.com/therewasaguy/p5.sound/blob/master/lib/p5.sound.js">here</a>.
- *
- * @module p5.sound
- * @submodule p5.sound
- * @for p5.sound
- * @main
- */
- /**
- * p5.sound developed by Jason Sigal for the Processing Foundation, Google Summer of Code 2014. The MIT License (MIT).
- *
- * http://github.com/therewasaguy/p5.sound
- *
- * Some of the many audio libraries & resources that inspire p5.sound:
- * - TONE.js (c) Yotam Mann, 2014. Licensed under The MIT License (MIT). https://github.com/TONEnoTONE/Tone.js
- * - buzz.js (c) Jay Salvat, 2013. Licensed under The MIT License (MIT). http://buzz.jaysalvat.com/
- * - Boris Smus Web Audio API book, 2013. Licensed under the Apache License http://www.apache.org/licenses/LICENSE-2.0
- * - wavesurfer.js https://github.com/katspaugh/wavesurfer.js
- * - Web Audio Components by Jordan Santell https://github.com/web-audio-components
- * - Wilm Thoben's Sound library for Processing https://github.com/processing/processing/tree/master/java/libraries/sound
- *
- * Web Audio API: http://w3.org/TR/webaudio/
- */
- var sndcore;
- sndcore = function () {
- 'use strict';
- /* AudioContext Monkeypatch
- Copyright 2013 Chris Wilson
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- (function (global, exports, perf) {
- exports = exports || {};
- 'use strict';
- function fixSetTarget(param) {
- if (!param)
- // if NYI, just return
- return;
- if (!param.setTargetAtTime)
- param.setTargetAtTime = param.setTargetValueAtTime;
- }
- if (window.hasOwnProperty('webkitAudioContext') && !window.hasOwnProperty('AudioContext')) {
- window.AudioContext = webkitAudioContext;
- if (typeof AudioContext.prototype.createGain !== 'function')
- AudioContext.prototype.createGain = AudioContext.prototype.createGainNode;
- if (typeof AudioContext.prototype.createDelay !== 'function')
- AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode;
- if (typeof AudioContext.prototype.createScriptProcessor !== 'function')
- AudioContext.prototype.createScriptProcessor = AudioContext.prototype.createScriptProcessor;
- if (typeof AudioContext.prototype.createPeriodicWave !== 'function')
- AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable;
- AudioContext.prototype.internal_createGain = AudioContext.prototype.createGain;
- AudioContext.prototype.createGain = function () {
- var node = this.internal_createGain();
- fixSetTarget(node.gain);
- return node;
- };
- AudioContext.prototype.internal_createDelay = AudioContext.prototype.createDelay;
- AudioContext.prototype.createDelay = function (maxDelayTime) {
- var node = maxDelayTime ? this.internal_createDelay(maxDelayTime) : this.internal_createDelay();
- fixSetTarget(node.delayTime);
- return node;
- };
- AudioContext.prototype.internal_createBufferSource = AudioContext.prototype.createBufferSource;
- AudioContext.prototype.createBufferSource = function () {
- var node = this.internal_createBufferSource();
- if (!node.start) {
- node.start = function (when, offset, duration) {
- if (offset || duration)
- this.noteGrainOn(when || 0, offset, duration);
- else
- this.noteOn(when || 0);
- };
- } else {
- node.internal_start = node.start;
- node.start = function (when, offset, duration) {
- if (typeof duration !== 'undefined')
- node.internal_start(when || 0, offset, duration);
- else
- node.internal_start(when || 0, offset || 0);
- };
- }
- if (!node.stop) {
- node.stop = function (when) {
- this.noteOff(when || 0);
- };
- } else {
- node.internal_stop = node.stop;
- node.stop = function (when) {
- node.internal_stop(when || 0);
- };
- }
- fixSetTarget(node.playbackRate);
- return node;
- };
- AudioContext.prototype.internal_createDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor;
- AudioContext.prototype.createDynamicsCompressor = function () {
- var node = this.internal_createDynamicsCompressor();
- fixSetTarget(node.threshold);
- fixSetTarget(node.knee);
- fixSetTarget(node.ratio);
- fixSetTarget(node.reduction);
- fixSetTarget(node.attack);
- fixSetTarget(node.release);
- return node;
- };
- AudioContext.prototype.internal_createBiquadFilter = AudioContext.prototype.createBiquadFilter;
- AudioContext.prototype.createBiquadFilter = function () {
- var node = this.internal_createBiquadFilter();
- fixSetTarget(node.frequency);
- fixSetTarget(node.detune);
- fixSetTarget(node.Q);
- fixSetTarget(node.gain);
- return node;
- };
- if (typeof AudioContext.prototype.createOscillator !== 'function') {
- AudioContext.prototype.internal_createOscillator = AudioContext.prototype.createOscillator;
- AudioContext.prototype.createOscillator = function () {
- var node = this.internal_createOscillator();
- if (!node.start) {
- node.start = function (when) {
- this.noteOn(when || 0);
- };
- } else {
- node.internal_start = node.start;
- node.start = function (when) {
- node.internal_start(when || 0);
- };
- }
- if (!node.stop) {
- node.stop = function (when) {
- this.noteOff(when || 0);
- };
- } else {
- node.internal_stop = node.stop;
- node.stop = function (when) {
- node.internal_stop(when || 0);
- };
- }
- if (!node.setPeriodicWave)
- node.setPeriodicWave = node.setWaveTable;
- fixSetTarget(node.frequency);
- fixSetTarget(node.detune);
- return node;
- };
- }
- }
- if (window.hasOwnProperty('webkitOfflineAudioContext') && !window.hasOwnProperty('OfflineAudioContext')) {
- window.OfflineAudioContext = webkitOfflineAudioContext;
- }
- return exports;
- }(window));
- // <-- end MonkeyPatch.
- // Create the Audio Context
- var audiocontext = new window.AudioContext();
- /**
- * <p>Returns the Audio Context for this sketch. Useful for users
- * who would like to dig deeper into the <a target='_blank' href=
- * 'http://webaudio.github.io/web-audio-api/'>Web Audio API
- * </a>.</p>
- *
- * @method getAudioContext
- * @return {Object} AudioContext for this sketch
- */
- p5.prototype.getAudioContext = function () {
- return audiocontext;
- };
- // Polyfill for AudioIn, also handled by p5.dom createCapture
- navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
- /**
- * Determine which filetypes are supported (inspired by buzz.js)
- * The audio element (el) will only be used to test browser support for various audio formats
- */
- var el = document.createElement('audio');
- p5.prototype.isSupported = function () {
- return !!el.canPlayType;
- };
- var isOGGSupported = function () {
- return !!el.canPlayType && el.canPlayType('audio/ogg; codecs="vorbis"');
- };
- var isMP3Supported = function () {
- return !!el.canPlayType && el.canPlayType('audio/mpeg;');
- };
- var isWAVSupported = function () {
- return !!el.canPlayType && el.canPlayType('audio/wav; codecs="1"');
- };
- var isAACSupported = function () {
- return !!el.canPlayType && (el.canPlayType('audio/x-m4a;') || el.canPlayType('audio/aac;'));
- };
- var isAIFSupported = function () {
- return !!el.canPlayType && el.canPlayType('audio/x-aiff;');
- };
- p5.prototype.isFileSupported = function (extension) {
- switch (extension.toLowerCase()) {
- case 'mp3':
- return isMP3Supported();
- case 'wav':
- return isWAVSupported();
- case 'ogg':
- return isOGGSupported();
- case 'aac', 'm4a', 'mp4':
- return isAACSupported();
- case 'aif', 'aiff':
- return isAIFSupported();
- default:
- return false;
- }
- };
- // if it is iOS, we have to have a user interaction to start Web Audio
- // http://paulbakaus.com/tutorials/html5/web-audio-on-ios/
- var iOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false;
- if (iOS) {
- var iosStarted = false;
- var startIOS = function () {
- if (iosStarted)
- return;
- // create empty buffer
- var buffer = audiocontext.createBuffer(1, 1, 22050);
- var source = audiocontext.createBufferSource();
- source.buffer = buffer;
- // connect to output (your speakers)
- source.connect(audiocontext.destination);
- // play the file
- source.start(0);
- console.log('start ios!');
- if (audiocontext.state === 'running') {
- iosStarted = true;
- }
- };
- document.addEventListener('touchend', startIOS, false);
- document.addEventListener('touchstart', startIOS, false);
- }
- }();
- var master;
- master = function () {
- 'use strict';
- /**
- * Master contains AudioContext and the master sound output.
- */
- var Master = function () {
- var audiocontext = p5.prototype.getAudioContext();
- this.input = audiocontext.createGain();
- this.output = audiocontext.createGain();
- //put a hard limiter on the output
- this.limiter = audiocontext.createDynamicsCompressor();
- this.limiter.threshold.value = 0;
- this.limiter.ratio.value = 20;
- this.audiocontext = audiocontext;
- this.output.disconnect();
- // an array of input sources
- this.inputSources = [];
- // connect input to limiter
- this.input.connect(this.limiter);
- // connect limiter to output
- this.limiter.connect(this.output);
- // meter is just for global Amplitude / FFT analysis
- this.meter = audiocontext.createGain();
- this.fftMeter = audiocontext.createGain();
- this.output.connect(this.meter);
- this.output.connect(this.fftMeter);
- // connect output to destination
- this.output.connect(this.audiocontext.destination);
- // an array of all sounds in the sketch
- this.soundArray = [];
- // an array of all musical parts in the sketch
- this.parts = [];
- // file extensions to search for
- this.extensions = [];
- };
- // create a single instance of the p5Sound / master output for use within this sketch
- var p5sound = new Master();
- /**
- * Returns a number representing the master amplitude (volume) for sound
- * in this sketch.
- *
- * @method getMasterVolume
- * @return {Number} Master amplitude (volume) for sound in this sketch.
- * Should be between 0.0 (silence) and 1.0.
- */
- p5.prototype.getMasterVolume = function () {
- return p5sound.output.gain.value;
- };
- /**
- * <p>Scale the output of all sound in this sketch</p>
- * Scaled between 0.0 (silence) and 1.0 (full volume).
- * 1.0 is the maximum amplitude of a digital sound, so multiplying
- * by greater than 1.0 may cause digital distortion. To
- * fade, provide a <code>rampTime</code> parameter. For more
- * complex fades, see the Env class.
- *
- * Alternately, you can pass in a signal source such as an
- * oscillator to modulate the amplitude with an audio signal.
- *
- * <p><b>How This Works</b>: When you load the p5.sound module, it
- * creates a single instance of p5sound. All sound objects in this
- * module output to p5sound before reaching your computer's output.
- * So if you change the amplitude of p5sound, it impacts all of the
- * sound in this module.</p>
- *
- * <p>If no value is provided, returns a Web Audio API Gain Node</p>
- *
- * @method masterVolume
- * @param {Number|Object} volume Volume (amplitude) between 0.0
- * and 1.0 or modulating signal/oscillator
- * @param {Number} [rampTime] Fade for t seconds
- * @param {Number} [timeFromNow] Schedule this event to happen at
- * t seconds in the future
- */
- p5.prototype.masterVolume = function (vol, rampTime, tFromNow) {
- if (typeof vol === 'number') {
- var rampTime = rampTime || 0;
- var tFromNow = tFromNow || 0;
- var now = p5sound.audiocontext.currentTime;
- var currentVol = p5sound.output.gain.value;
- p5sound.output.gain.cancelScheduledValues(now + tFromNow);
- p5sound.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow);
- p5sound.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime);
- } else if (vol) {
- vol.connect(p5sound.output.gain);
- } else {
- // return the Gain Node
- return p5sound.output.gain;
- }
- };
- /**
- * `p5.soundOut` is the p5.sound master output. It sends output to
- * the destination of this window's web audio context. It contains
- * Web Audio API nodes including a dyanmicsCompressor (<code>.limiter</code>),
- * and Gain Nodes for <code>.input</code> and <code>.output</code>.
- *
- * @property soundOut
- * @type {Object}
- */
- p5.prototype.soundOut = p5.soundOut = p5sound;
- /**
- * a silent connection to the DesinationNode
- * which will ensure that anything connected to it
- * will not be garbage collected
- *
- * @private
- */
- p5.soundOut._silentNode = p5sound.audiocontext.createGain();
- p5.soundOut._silentNode.gain.value = 0;
- p5.soundOut._silentNode.connect(p5sound.audiocontext.destination);
- return p5sound;
- }(sndcore);
- var helpers;
- helpers = function () {
- 'use strict';
- var p5sound = master;
- /**
- * Returns a number representing the sample rate, in samples per second,
- * of all sound objects in this audio context. It is determined by the
- * sampling rate of your operating system's sound card, and it is not
- * currently possile to change.
- * It is often 44100, or twice the range of human hearing.
- *
- * @method sampleRate
- * @return {Number} samplerate samples per second
- */
- p5.prototype.sampleRate = function () {
- return p5sound.audiocontext.sampleRate;
- };
- /**
- * Returns the closest MIDI note value for
- * a given frequency.
- *
- * @param {Number} frequency A freqeuncy, for example, the "A"
- * above Middle C is 440Hz
- * @return {Number} MIDI note value
- */
- p5.prototype.freqToMidi = function (f) {
- var mathlog2 = Math.log(f / 440) / Math.log(2);
- var m = Math.round(12 * mathlog2) + 57;
- return m;
- };
- /**
- * Returns the frequency value of a MIDI note value.
- * General MIDI treats notes as integers where middle C
- * is 60, C# is 61, D is 62 etc. Useful for generating
- * musical frequencies with oscillators.
- *
- * @method midiToFreq
- * @param {Number} midiNote The number of a MIDI note
- * @return {Number} Frequency value of the given MIDI note
- * @example
- * <div><code>
- * var notes = [60, 64, 67, 72];
- * var i = 0;
- *
- * function setup() {
- * osc = new p5.Oscillator('Triangle');
- * osc.start();
- * frameRate(1);
- * }
- *
- * function draw() {
- * var freq = midiToFreq(notes[i]);
- * osc.freq(freq);
- * i++;
- * if (i >= notes.length){
- * i = 0;
- * }
- * }
- * </code></div>
- */
- p5.prototype.midiToFreq = function (m) {
- return 440 * Math.pow(2, (m - 69) / 12);
- };
- /**
- * List the SoundFile formats that you will include. LoadSound
- * will search your directory for these extensions, and will pick
- * a format that is compatable with the client's web browser.
- * <a href="http://media.io/">Here</a> is a free online file
- * converter.
- *
- * @method soundFormats
- * @param {String} formats i.e. 'mp3', 'wav', 'ogg'
- * @example
- * <div><code>
- * function preload() {
- * // set the global sound formats
- * soundFormats('mp3', 'ogg');
- *
- * // load either beatbox.mp3, or .ogg, depending on browser
- * mySound = loadSound('../sounds/beatbox.mp3');
- * }
- *
- * function setup() {
- * mySound.play();
- * }
- * </code></div>
- */
- p5.prototype.soundFormats = function () {
- // reset extensions array
- p5sound.extensions = [];
- // add extensions
- for (var i = 0; i < arguments.length; i++) {
- arguments[i] = arguments[i].toLowerCase();
- if ([
- 'mp3',
- 'wav',
- 'ogg',
- 'm4a',
- 'aac'
- ].indexOf(arguments[i]) > -1) {
- p5sound.extensions.push(arguments[i]);
- } else {
- throw arguments[i] + ' is not a valid sound format!';
- }
- }
- };
- p5.prototype.disposeSound = function () {
- for (var i = 0; i < p5sound.soundArray.length; i++) {
- p5sound.soundArray[i].dispose();
- }
- };
- // register removeSound to dispose of p5sound SoundFiles, Convolvers,
- // Oscillators etc when sketch ends
- p5.prototype.registerMethod('remove', p5.prototype.disposeSound);
- p5.prototype._checkFileFormats = function (paths) {
- var path;
- // if path is a single string, check to see if extension is provided
- if (typeof paths === 'string') {
- path = paths;
- // see if extension is provided
- var extTest = path.split('.').pop();
- // if an extension is provided...
- if ([
- 'mp3',
- 'wav',
- 'ogg',
- 'm4a',
- 'aac'
- ].indexOf(extTest) > -1) {
- var supported = p5.prototype.isFileSupported(extTest);
- if (supported) {
- path = path;
- } else {
- var pathSplit = path.split('.');
- var pathCore = pathSplit[pathSplit.length - 1];
- for (var i = 0; i < p5sound.extensions.length; i++) {
- var extension = p5sound.extensions[i];
- var supported = p5.prototype.isFileSupported(extension);
- if (supported) {
- pathCore = '';
- if (pathSplit.length === 2) {
- pathCore += pathSplit[0];
- }
- for (var i = 1; i <= pathSplit.length - 2; i++) {
- var p = pathSplit[i];
- pathCore += '.' + p;
- }
- path = pathCore += '.';
- path = path += extension;
- break;
- }
- }
- }
- } else {
- for (var i = 0; i < p5sound.extensions.length; i++) {
- var extension = p5sound.extensions[i];
- var supported = p5.prototype.isFileSupported(extension);
- if (supported) {
- path = path + '.' + extension;
- break;
- }
- }
- }
- } else if (typeof paths === 'object') {
- for (var i = 0; i < paths.length; i++) {
- var extension = paths[i].split('.').pop();
- var supported = p5.prototype.isFileSupported(extension);
- if (supported) {
- // console.log('.'+extension + ' is ' + supported +
- // ' supported by your browser.');
- path = paths[i];
- break;
- }
- }
- }
- return path;
- };
- /**
- * Used by Osc and Env to chain signal math
- */
- p5.prototype._mathChain = function (o, math, thisChain, nextChain, type) {
- // if this type of math already exists in the chain, replace it
- for (var i in o.mathOps) {
- if (o.mathOps[i] instanceof type) {
- o.mathOps[i].dispose();
- thisChain = i;
- if (thisChain < o.mathOps.length - 1) {
- nextChain = o.mathOps[i + 1];
- }
- }
- }
- o.mathOps[thisChain - 1].disconnect();
- o.mathOps[thisChain - 1].connect(math);
- math.connect(nextChain);
- o.mathOps[thisChain] = math;
- return o;
- };
- }(master);
- var errorHandler;
- errorHandler = function () {
- 'use strict';
- /**
- * Helper function to generate an error
- * with a custom stack trace that points to the sketch
- * and removes other parts of the stack trace.
- *
- * @private
- *
- * @param {String} name custom error name
- * @param {String} errorTrace custom error trace
- * @param {String} failedPath path to the file that failed to load
- * @property {String} name custom error name
- * @property {String} message custom error message
- * @property {String} stack trace the error back to a line in the user's sketch.
- * Note: this edits out stack trace within p5.js and p5.sound.
- * @property {String} originalStack unedited, original stack trace
- * @property {String} failedPath path to the file that failed to load
- * @return {Error} returns a custom Error object
- */
- var CustomError = function (name, errorTrace, failedPath) {
- var err = new Error();
- var tempStack, splitStack;
- err.name = name;
- err.originalStack = err.stack + errorTrace;
- tempStack = err.stack + errorTrace;
- err.failedPath = failedPath;
- // only print the part of the stack trace that refers to the user code:
- var splitStack = tempStack.split('\n');
- splitStack = splitStack.filter(function (ln) {
- return !ln.match(/(p5.|native code|globalInit)/g);
- });
- err.stack = splitStack.join('\n');
- return err;
- };
- return CustomError;
- }();
- var panner;
- panner = function () {
- 'use strict';
- var p5sound = master;
- var ac = p5sound.audiocontext;
- // Stereo panner
- // if there is a stereo panner node use it
- if (typeof ac.createStereoPanner !== 'undefined') {
- p5.Panner = function (input, output, numInputChannels) {
- this.stereoPanner = this.input = ac.createStereoPanner();
- input.connect(this.stereoPanner);
- this.stereoPanner.connect(output);
- };
- p5.Panner.prototype.pan = function (val, tFromNow) {
- var time = tFromNow || 0;
- var t = ac.currentTime + time;
- this.stereoPanner.pan.linearRampToValueAtTime(val, t);
- };
- p5.Panner.prototype.inputChannels = function (numChannels) {
- };
- p5.Panner.prototype.connect = function (obj) {
- this.stereoPanner.connect(obj);
- };
- p5.Panner.prototype.disconnect = function (obj) {
- this.stereoPanner.disconnect();
- };
- } else {
- // if there is no createStereoPanner object
- // such as in safari 7.1.7 at the time of writing this
- // use this method to create the effect
- p5.Panner = function (input, output, numInputChannels) {
- this.input = ac.createGain();
- input.connect(this.input);
- this.left = ac.createGain();
- this.right = ac.createGain();
- this.left.channelInterpretation = 'discrete';
- this.right.channelInterpretation = 'discrete';
- // if input is stereo
- if (numInputChannels > 1) {
- this.splitter = ac.createChannelSplitter(2);
- this.input.connect(this.splitter);
- this.splitter.connect(this.left, 1);
- this.splitter.connect(this.right, 0);
- } else {
- this.input.connect(this.left);
- this.input.connect(this.right);
- }
- this.output = ac.createChannelMerger(2);
- this.left.connect(this.output, 0, 1);
- this.right.connect(this.output, 0, 0);
- this.output.connect(output);
- };
- // -1 is left, +1 is right
- p5.Panner.prototype.pan = function (val, tFromNow) {
- var time = tFromNow || 0;
- var t = ac.currentTime + time;
- var v = (val + 1) / 2;
- var rightVal = Math.cos(v * Math.PI / 2);
- var leftVal = Math.sin(v * Math.PI / 2);
- this.left.gain.linearRampToValueAtTime(leftVal, t);
- this.right.gain.linearRampToValueAtTime(rightVal, t);
- };
- p5.Panner.prototype.inputChannels = function (numChannels) {
- if (numChannels === 1) {
- this.input.disconnect();
- this.input.connect(this.left);
- this.input.connect(this.right);
- } else if (numChannels === 2) {
- if (typeof (this.splitter === 'undefined')) {
- this.splitter = ac.createChannelSplitter(2);
- }
- this.input.disconnect();
- this.input.connect(this.splitter);
- this.splitter.connect(this.left, 1);
- this.splitter.connect(this.right, 0);
- }
- };
- p5.Panner.prototype.connect = function (obj) {
- this.output.connect(obj);
- };
- p5.Panner.prototype.disconnect = function (obj) {
- this.output.disconnect();
- };
- }
- // 3D panner
- p5.Panner3D = function (input, output) {
- var panner3D = ac.createPanner();
- panner3D.panningModel = 'HRTF';
- panner3D.distanceModel = 'linear';
- panner3D.setPosition(0, 0, 0);
- input.connect(panner3D);
- panner3D.connect(output);
- panner3D.pan = function (xVal, yVal, zVal) {
- panner3D.setPosition(xVal, yVal, zVal);
- };
- return panner3D;
- };
- }(master);
- var soundfile;
- soundfile = function () {
- 'use strict';
- var CustomError = errorHandler;
- var p5sound = master;
- var ac = p5sound.audiocontext;
- /**
- * <p>SoundFile object with a path to a file.</p>
- *
- * <p>The p5.SoundFile may not be available immediately because
- * it loads the file information asynchronously.</p>
- *
- * <p>To do something with the sound as soon as it loads
- * pass the name of a function as the second parameter.</p>
- *
- * <p>Only one file path is required. However, audio file formats
- * (i.e. mp3, ogg, wav and m4a/aac) are not supported by all
- * web browsers. If you want to ensure compatability, instead of a single
- * file path, you may include an Array of filepaths, and the browser will
- * choose a format that works.</p>
- *
- * @class p5.SoundFile
- * @constructor
- * @param {String|Array} path path to a sound file (String). Optionally,
- * you may include multiple file formats in
- * an array. Alternately, accepts an object
- * from the HTML5 File API, or a p5.File.
- * @param {Function} [successCallback] Name of a function to call once file loads
- * @param {Function} [errorCallback] Name of a function to call if file fails to
- * load. This function will receive an error or
- * XMLHttpRequest object with information
- * about what went wrong.
- * @param {Function} [whileLoadingCallback] Name of a function to call while file
- * is loading. That function will
- * receive progress of the request to
- * load the sound file
- * (between 0 and 1) as its first
- * parameter. This progress
- * does not account for the additional
- * time needed to decode the audio data.
- *
- * @return {Object} p5.SoundFile Object
- * @example
- * <div><code>
- *
- * function preload() {
- * mySound = loadSound('assets/doorbell.mp3');
- * }
- *
- * function setup() {
- * mySound.setVolume(0.1);
- * mySound.play();
- * }
- *
- * </code></div>
- */
- p5.SoundFile = function (paths, onload, onerror, whileLoading) {
- if (typeof paths !== 'undefined') {
- if (typeof paths == 'string' || typeof paths[0] == 'string') {
- var path = p5.prototype._checkFileFormats(paths);
- this.url = path;
- } else if (typeof paths == 'object') {
- if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
- // The File API isn't supported in this browser
- throw 'Unable to load file because the File API is not supported';
- }
- }
- // if type is a p5.File...get the actual file
- if (paths.file) {
- paths = paths.file;
- }
- this.file = paths;
- }
- // private _onended callback, set by the method: onended(callback)
- this._onended = function () {
- };
- this._looping = false;
- this._playing = false;
- this._paused = false;
- this._pauseTime = 0;
- // cues for scheduling events with addCue() removeCue()
- this._cues = [];
- // position of the most recently played sample
- this._lastPos = 0;
- this._counterNode;
- this._scopeNode;
- // array of sources so that they can all be stopped!
- this.bufferSourceNodes = [];
- // current source
- this.bufferSourceNode = null;
- this.buffer = null;
- this.playbackRate = 1;
- this.gain = 1;
- this.input = p5sound.audiocontext.createGain();
- this.output = p5sound.audiocontext.createGain();
- this.reversed = false;
- // start and end of playback / loop
- this.startTime = 0;
- this.endTime = null;
- this.pauseTime = 0;
- // "restart" would stop playback before retriggering
- this.mode = 'sustain';
- // time that playback was started, in millis
- this.startMillis = null;
- // stereo panning
- this.panPosition = 0;
- this.panner = new p5.Panner(this.output, p5sound.input, 2);
- // it is possible to instantiate a soundfile with no path
- if (this.url || this.file) {
- this.load(onload, onerror);
- }
- // add this p5.SoundFile to the soundArray
- p5sound.soundArray.push(this);
- if (typeof whileLoading === 'function') {
- this._whileLoading = whileLoading;
- } else {
- this._whileLoading = function () {
- };
- }
- };
- // register preload handling of loadSound
- p5.prototype.registerPreloadMethod('loadSound', p5.prototype);
- /**
- * loadSound() returns a new p5.SoundFile from a specified
- * path. If called during preload(), the p5.SoundFile will be ready
- * to play in time for setup() and draw(). If called outside of
- * preload, the p5.SoundFile will not be ready immediately, so
- * loadSound accepts a callback as the second parameter. Using a
- * <a href="https://github.com/processing/p5.js/wiki/Local-server">
- * local server</a> is recommended when loading external files.
- *
- * @method loadSound
- * @param {String|Array} path Path to the sound file, or an array with
- * paths to soundfiles in multiple formats
- * i.e. ['sound.ogg', 'sound.mp3'].
- * Alternately, accepts an object: either
- * from the HTML5 File API, or a p5.File.
- * @param {Function} [successCallback] Name of a function to call once file loads
- * @param {Function} [errorCallback] Name of a function to call if there is
- * an error loading the file.
- * @param {Function} [whileLoading] Name of a function to call while file is loading.
- * This function will receive the percentage loaded
- * so far, from 0.0 to 1.0.
- * @return {SoundFile} Returns a p5.SoundFile
- * @example
- * <div><code>
- * function preload() {
- * mySound = loadSound('assets/doorbell.mp3');
- * }
- *
- * function setup() {
- * mySound.setVolume(0.1);
- * mySound.play();
- * }
- * </code></div>
- */
- p5.prototype.loadSound = function (path, callback, onerror, whileLoading) {
- // if loading locally without a server
- if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') {
- alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS');
- }
- var s = new p5.SoundFile(path, callback, onerror, whileLoading);
- return s;
- };
- /**
- * This is a helper function that the p5.SoundFile calls to load
- * itself. Accepts a callback (the name of another function)
- * as an optional parameter.
- *
- * @private
- * @param {Function} [successCallback] Name of a function to call once file loads
- * @param {Function} [errorCallback] Name of a function to call if there is an error
- */
- p5.SoundFile.prototype.load = function (callback, errorCallback) {
- var loggedError = false;
- var self = this;
- var errorTrace = new Error().stack;
- if (this.url != undefined && this.url != '') {
- var request = new XMLHttpRequest();
- request.addEventListener('progress', function (evt) {
- self._updateProgress(evt);
- }, false);
- request.open('GET', this.url, true);
- request.responseType = 'arraybuffer';
- request.onload = function () {
- if (request.status == 200) {
- // on sucess loading file:
- ac.decodeAudioData(request.response, // success decoding buffer:
- function (buff) {
- self.buffer = buff;
- self.panner.inputChannels(buff.numberOfChannels);
- if (callback) {
- callback(self);
- }
- }, // error decoding buffer. "e" is undefined in Chrome 11/22/2015
- function (e) {
- var err = new CustomError('decodeAudioData', errorTrace, self.url);
- var msg = 'AudioContext error at decodeAudioData for ' + self.url;
- if (errorCallback) {
- err.msg = msg;
- errorCallback(err);
- } else {
- console.error(msg + '\n The error stack trace includes: \n' + err.stack);
- }
- });
- } else {
- var err = new CustomError('loadSound', errorTrace, self.url);
- var msg = 'Unable to load ' + self.url + '. The request status was: ' + request.status + ' (' + request.statusText + ')';
- if (errorCallback) {
- err.message = msg;
- errorCallback(err);
- } else {
- console.error(msg + '\n The error stack trace includes: \n' + err.stack);
- }
- }
- };
- // if there is another error, aside from 404...
- request.onerror = function (e) {
- var err = new CustomError('loadSound', errorTrace, self.url);
- var msg = 'There was no response from the server at ' + self.url + '. Check the url and internet connectivity.';
- if (errorCallback) {
- err.message = msg;
- errorCallback(err);
- } else {
- console.error(msg + '\n The error stack trace includes: \n' + err.stack);
- }
- };
- request.send();
- } else if (this.file != undefined) {
- var reader = new FileReader();
- var self = this;
- reader.onload = function () {
- ac.decodeAudioData(reader.result, function (buff) {
- self.buffer = buff;
- self.panner.inputChannels(buff.numberOfChannels);
- if (callback) {
- callback(self);
- }
- });
- };
- reader.onerror = function (e) {
- if (onerror)
- onerror(e);
- };
- reader.readAsArrayBuffer(this.file);
- }
- };
- // TO DO: use this method to create a loading bar that shows progress during file upload/decode.
- p5.SoundFile.prototype._updateProgress = function (evt) {
- if (evt.lengthComputable) {
- var percentComplete = evt.loaded / evt.total * 0.99;
- this._whileLoading(percentComplete, evt);
- } else {
- // Unable to compute progress information since the total size is unknown
- this._whileLoading('size unknown');
- }
- };
- /**
- * Returns true if the sound file finished loading successfully.
- *
- * @method isLoaded
- * @return {Boolean}
- */
- p5.SoundFile.prototype.isLoaded = function () {
- if (this.buffer) {
- return true;
- } else {
- return false;
- }
- };
- /**
- * Play the p5.SoundFile
- *
- * @method play
- * @param {Number} [startTime] (optional) schedule playback to start (in seconds from now).
- * @param {Number} [rate] (optional) playback rate
- * @param {Number} [amp] (optional) amplitude (volume)
- * of playback
- * @param {Number} [cueStart] (optional) cue start time in seconds
- * @param {Number} [duration] (optional) duration of playback in seconds
- */
- p5.SoundFile.prototype.play = function (time, rate, amp, _cueStart, duration) {
- var self = this;
- var now = p5sound.audiocontext.currentTime;
- var cueStart, cueEnd;
- var time = time || 0;
- if (time < 0) {
- time = 0;
- }
- time = time + now;
- // TO DO: if already playing, create array of buffers for easy stop()
- if (this.buffer) {
- // reset the pause time (if it was paused)
- this._pauseTime = 0;
- // handle restart playmode
- if (this.mode === 'restart' && this.buffer && this.bufferSourceNode) {
- var now = p5sound.audiocontext.currentTime;
- this.bufferSourceNode.stop(time);
- this._counterNode.stop(time);
- }
- // set playback rate
- if (rate)
- this.playbackRate = rate;
- // make a new source and counter. They are automatically assigned playbackRate and buffer
- this.bufferSourceNode = this._initSourceNode();
- // garbage collect counterNode and create a new one
- if (this._counterNode)
- this._counterNode = undefined;
- this._counterNode = this._initCounterNode();
- if (_cueStart) {
- if (_cueStart >= 0 && _cueStart < this.buffer.duration) {
- // this.startTime = cueStart;
- cueStart = _cueStart;
- } else {
- throw 'start time out of range';
- }
- } else {
- cueStart = 0;
- }
- if (duration) {
- // if duration is greater than buffer.duration, just play entire file anyway rather than throw an error
- duration = duration <= this.buffer.duration - cueStart ? duration : this.buffer.duration;
- } else {
- duration = this.buffer.duration - cueStart;
- }
- // TO DO: Fix this. It broke in Safari
- //
- // method of controlling gain for individual bufferSourceNodes, without resetting overall soundfile volume
- // if (typeof(this.bufferSourceNode.gain === 'undefined' ) ) {
- // this.bufferSourceNode.gain = p5sound.audiocontext.createGain();
- // }
- // this.bufferSourceNode.connect(this.bufferSourceNode.gain);
- // set local amp if provided, otherwise 1
- var a = amp || 1;
- // this.bufferSourceNode.gain.gain.setValueAtTime(a, p5sound.audiocontext.currentTime);
- // this.bufferSourceNode.gain.connect(this.output);
- this.bufferSourceNode.connect(this.output);
- this.output.gain.value = a;
- // if it was paused, play at the pause position
- if (this._paused) {
- this.bufferSourceNode.start(time, this.pauseTime, duration);
- this._counterNode.start(time, this.pauseTime, duration);
- } else {
- this.bufferSourceNode.start(time, cueStart, duration);
- this._counterNode.start(time, cueStart, duration);
- }
- this._playing = true;
- this._paused = false;
- // add source to sources array, which is used in stopAll()
- this.bufferSourceNodes.push(this.bufferSourceNode);
- this.bufferSourceNode._arrayIndex = this.bufferSourceNodes.length - 1;
- // delete this.bufferSourceNode from the sources array when it is done playing:
- var clearOnEnd = function (e) {
- this._playing = false;
- this.removeEventListener('ended', clearOnEnd, false);
- // call the onended callback
- self._onended(self);
- self.bufferSourceNodes.forEach(function (n, i) {
- if (n._playing === false) {
- self.bufferSourceNodes.splice(i);
- }
- });
- if (self.bufferSourceNodes.length === 0) {
- self._playing = false;
- }
- };
- this.bufferSourceNode.onended = clearOnEnd;
- } else {
- throw 'not ready to play file, buffer has yet to load. Try preload()';
- }
- // if looping, will restart at original time
- this.bufferSourceNode.loop = this._looping;
- this._counterNode.loop = this._looping;
- if (this._looping === true) {
- var cueEnd = cueStart + duration;
- this.bufferSourceNode.loopStart = cueStart;
- this.bufferSourceNode.loopEnd = cueEnd;
- this._counterNode.loopStart = cueStart;
- this._counterNode.loopEnd = cueEnd;
- }
- };
- /**
- * p5.SoundFile has two play modes: <code>restart</code> and
- * <code>sustain</code>. Play Mode determines what happens to a
- * p5.SoundFile if it is triggered while in the middle of playback.
- * In sustain mode, playback will continue simultaneous to the
- * new playback. In restart mode, play() will stop playback
- * and start over. Sustain is the default mode.
- *
- * @method playMode
- * @param {String} str 'restart' or 'sustain'
- * @example
- * <div><code>
- * function setup(){
- * mySound = loadSound('assets/Damscray_DancingTiger.mp3');
- * }
- * function mouseClicked() {
- * mySound.playMode('sustain');
- * mySound.play();
- * }
- * function keyPressed() {
- * mySound.playMode('restart');
- * mySound.play();
- * }
- *
- * </code></div>
- */
- p5.SoundFile.prototype.playMode = function (str) {
- var s = str.toLowerCase();
- // if restart, stop all other sounds from playing
- if (s === 'restart' && this.buffer && this.bufferSourceNode) {
- for (var i = 0; i < this.bufferSourceNodes.length - 1; i++) {
- var now = p5sound.audiocontext.currentTime;
- this.bufferSourceNodes[i].stop(now);
- }
- }
- // set play mode to effect future playback
- if (s === 'restart' || s === 'sustain') {
- this.mode = s;
- } else {
- throw 'Invalid play mode. Must be either "restart" or "sustain"';
- }
- };
- /**
- * Pauses a file that is currently playing. If the file is not
- * playing, then nothing will happen.
- *
- * After pausing, .play() will resume from the paused
- * position.
- * If p5.SoundFile had been set to loop before it was paused,
- * it will continue to loop after it is unpaused with .play().
- *
- * @method pause
- * @param {Number} [startTime] (optional) schedule event to occur
- * seconds from now
- * @example
- * <div><code>
- * var soundFile;
- *
- * function preload() {
- * soundFormats('ogg', 'mp3');
- * soundFile = loadSound('assets/Damscray_-_Dancing_Tiger_02.mp3');
- * }
- * function setup() {
- * background(0, 255, 0);
- * soundFile.setVolume(0.1);
- * soundFile.loop();
- * }
- * function keyTyped() {
- * if (key == 'p') {
- * soundFile.pause();
- * background(255, 0, 0);
- * }
- * }
- *
- * function keyReleased() {
- * if (key == 'p') {
- * soundFile.play();
- * background(0, 255, 0);
- * }
- * }
- * </code>
- * </div>
- */
- p5.SoundFile.prototype.pause = function (time) {
- var now = p5sound.audiocontext.currentTime;
- var time = time || 0;
- var pTime = time + now;
- if (this.isPlaying() && this.buffer && this.bufferSourceNode) {
- this.pauseTime = this.currentTime();
- this.bufferSourceNode.stop(pTime);
- this._counterNode.stop(pTime);
- this._paused = true;
- this._playing = false;
- this._pauseTime = this.currentTime();
- } else {
- this._pauseTime = 0;
- }
- };
- /**
- * Loop the p5.SoundFile. Accepts optional parameters to set the
- * playback rate, playback volume, loopStart, loopEnd.
- *
- * @method loop
- * @param {Number} [startTime] (optional) schedule event to occur
- * seconds from now
- * @param {Number} [rate] (optional) playback rate
- * @param {Number} [amp] (optional) playback volume
- * @param {Number} [cueLoopStart](optional) startTime in seconds
- * @param {Number} [duration] (optional) loop duration in seconds
- */
- p5.SoundFile.prototype.loop = function (startTime, rate, amp, loopStart, duration) {
- this._looping = true;
- this.play(startTime, rate, amp, loopStart, duration);
- };
- /**
- * Set a p5.SoundFile's looping flag to true or false. If the sound
- * is currently playing, this change will take effect when it
- * reaches the end of the current playback.
- *
- * @param {Boolean} Boolean set looping to true or false
- */
- p5.SoundFile.prototype.setLoop = function (bool) {
- if (bool === true) {
- this._looping = true;
- } else if (bool === false) {
- this._looping = false;
- } else {
- throw 'Error: setLoop accepts either true or false';
- }
- if (this.bufferSourceNode) {
- this.bufferSourceNode.loop = this._looping;
- this._counterNode.loop = this._looping;
- }
- };
- /**
- * Returns 'true' if a p5.SoundFile is currently looping and playing, 'false' if not.
- *
- * @return {Boolean}
- */
- p5.SoundFile.prototype.isLooping = function () {
- if (!this.bufferSourceNode) {
- return false;
- }
- if (this._looping === true && this.isPlaying() === true) {
- return true;
- }
- return false;
- };
- /**
- * Returns true if a p5.SoundFile is playing, false if not (i.e.
- * paused or stopped).
- *
- * @method isPlaying
- * @return {Boolean}
- */
- p5.SoundFile.prototype.isPlaying = function () {
- return this._playing;
- };
- /**
- * Returns true if a p5.SoundFile is paused, false if not (i.e.
- * playing or stopped).
- *
- * @method isPaused
- * @return {Boolean}
- */
- p5.SoundFile.prototype.isPaused = function () {
- return this._paused;
- };
- /**
- * Stop soundfile playback.
- *
- * @method stop
- * @param {Number} [startTime] (optional) schedule event to occur
- * in seconds from now
- */
- p5.SoundFile.prototype.stop = function (timeFromNow) {
- var time = timeFromNow || 0;
- if (this.mode == 'sustain') {
- this.stopAll(time);
- this._playing = false;
- this.pauseTime = 0;
- this._paused = false;
- } else if (this.buffer && this.bufferSourceNode) {
- var now = p5sound.audiocontext.currentTime;
- var t = time || 0;
- this.pauseTime = 0;
- this.bufferSourceNode.stop(now + t);
- this._counterNode.stop(now + t);
- this._playing = false;
- this._paused = false;
- }
- };
- /**
- * Stop playback on all of this soundfile's sources.
- * @private
- */
- p5.SoundFile.prototype.stopAll = function (_time) {
- var now = p5sound.audiocontext.currentTime;
- var time = _time || 0;
- if (this.buffer && this.bufferSourceNode) {
- for (var i = 0; i < this.bufferSourceNodes.length; i++) {
- if (typeof this.bufferSourceNodes[i] != undefined) {
- try {
- this.bufferSourceNodes[i].onended = function () {
- };
- this.bufferSourceNodes[i].stop(now + time);
- } catch (e) {
- }
- }
- }
- this._counterNode.stop(now + time);
- this._onended(this);
- }
- };
- /**
- * Multiply the output volume (amplitude) of a sound file
- * between 0.0 (silence) and 1.0 (full volume).
- * 1.0 is the maximum amplitude of a digital sound, so multiplying
- * by greater than 1.0 may cause digital distortion. To
- * fade, provide a <code>rampTime</code> parameter. For more
- * complex fades, see the Env class.
- *
- * Alternately, you can pass in a signal source such as an
- * oscillator to modulate the amplitude with an audio signal.
- *
- * @method setVolume
- * @param {Number|Object} volume Volume (amplitude) between 0.0
- * and 1.0 or modulating signal/oscillator
- * @param {Number} [rampTime] Fade for t seconds
- * @param {Number} [timeFromNow] Schedule this event to happen at
- * t seconds in the future
- */
- p5.SoundFile.prototype.setVolume = function (vol, rampTime, tFromNow) {
- if (typeof vol === 'number') {
- var rampTime = rampTime || 0;
- var tFromNow = tFromNow || 0;
- var now = p5sound.audiocontext.currentTime;
- var currentVol = this.output.gain.value;
- this.output.gain.cancelScheduledValues(now + tFromNow);
- this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow);
- this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime);
- } else if (vol) {
- vol.connect(this.output.gain);
- } else {
- // return the Gain Node
- return this.output.gain;
- }
- };
- // same as setVolume, to match Processing Sound
- p5.SoundFile.prototype.amp = p5.SoundFile.prototype.setVolume;
- // these are the same thing
- p5.SoundFile.prototype.fade = p5.SoundFile.prototype.setVolume;
- p5.SoundFile.prototype.getVolume = function () {
- return this.output.gain.value;
- };
- /**
- * Set the stereo panning of a p5.sound object to
- * a floating point number between -1.0 (left) and 1.0 (right).
- * Default is 0.0 (center).
- *
- * @method pan
- * @param {Number} [panValue] Set the stereo panner
- * @param {Number} timeFromNow schedule this event to happen
- * seconds from now
- * @example
- * <div><code>
- *
- * var ball = {};
- * var soundFile;
- *
- * function setup() {
- * soundFormats('ogg', 'mp3');
- * soundFile = loadSound('assets/beatbox.mp3');
- * }
- *
- * function draw() {
- * background(0);
- * ball.x = constrain(mouseX, 0, width);
- * ellipse(ball.x, height/2, 20, 20)
- * }
- *
- * function mousePressed(){
- * // map the ball's x location to a panning degree
- * // between -1.0 (left) and 1.0 (right)
- * var panning = map(ball.x, 0., width,-1.0, 1.0);
- * soundFile.pan(panning);
- * soundFile.play();
- * }
- * </div></code>
- */
- p5.SoundFile.prototype.pan = function (pval, tFromNow) {
- this.panPosition = pval;
- this.panner.pan(pval, tFromNow);
- };
- /**
- * Returns the current stereo pan position (-1.0 to 1.0)
- *
- * @return {Number} Returns the stereo pan setting of the Oscillator
- * as a number between -1.0 (left) and 1.0 (right).
- * 0.0 is center and default.
- */
- p5.SoundFile.prototype.getPan = function () {
- return this.panPosition;
- };
- /**
- * Set the playback rate of a sound file. Will change the speed and the pitch.
- * Values less than zero will reverse the audio buffer.
- *
- * @method rate
- * @param {Number} [playbackRate] Set the playback rate. 1.0 is normal,
- * .5 is half-speed, 2.0 is twice as fast.
- * Values less than zero play backwards.
- * @example
- * <div><code>
- * var song;
- *
- * function preload() {
- * song = loadSound('assets/Damscray_DancingTiger.mp3');
- * }
- *
- * function setup() {
- * song.loop();
- * }
- *
- * function draw() {
- * background(200);
- *
- * // Set the rate to a range between 0.1 and 4
- * // Changing the rate also alters the pitch
- * var speed = map(mouseY, 0.1, height, 0, 2);
- * speed = constrain(speed, 0.01, 4);
- * song.rate(speed);
- *
- * // Draw a circle to show what is going on
- * stroke(0);
- * fill(51, 100);
- * ellipse(mouseX, 100, 48, 48);
- * }
- *
- * </code>
- * </div>
- *
- */
- p5.SoundFile.prototype.rate = function (playbackRate) {
- if (this.playbackRate === playbackRate && this.bufferSourceNode) {
- if (this.bufferSourceNode.playbackRate.value === playbackRate) {
- return;
- }
- }
- this.playbackRate = playbackRate;
- var rate = playbackRate;
- if (this.playbackRate === 0 && this._playing) {
- this.pause();
- }
- if (this.playbackRate < 0 && !this.reversed) {
- var cPos = this.currentTime();
- var cRate = this.bufferSourceNode.playbackRate.value;
- // this.pause();
- this.reverseBuffer();
- rate = Math.abs(playbackRate);
- var newPos = (cPos - this.duration()) / rate;
- this.pauseTime = newPos;
- } else if (this.playbackRate > 0 && this.reversed) {
- this.reverseBuffer();
- }
- if (this.bufferSourceNode) {
- var now = p5sound.audiocontext.currentTime;
- this.bufferSourceNode.playbackRate.cancelScheduledValues(now);
- this.bufferSourceNode.playbackRate.linearRampToValueAtTime(Math.abs(rate), now);
- this._counterNode.playbackRate.cancelScheduledValues(now);
- this._counterNode.playbackRate.linearRampToValueAtTime(Math.abs(rate), now);
- }
- };
- // TO DO: document this
- p5.SoundFile.prototype.setPitch = function (num) {
- var newPlaybackRate = midiToFreq(num) / midiToFreq(60);
- this.rate(newPlaybackRate);
- };
- p5.SoundFile.prototype.getPlaybackRate = function () {
- return this.playbackRate;
- };
- /**
- * Returns the duration of a sound file in seconds.
- *
- * @method duration
- * @return {Number} The duration of the soundFile in seconds.
- */
- p5.SoundFile.prototype.duration = function () {
- // Return Duration
- if (this.buffer) {
- return this.buffer.duration;
- } else {
- return 0;
- }
- };
- /**
- * Return the current position of the p5.SoundFile playhead, in seconds.
- * Note that if you change the playbackRate while the p5.SoundFile is
- * playing, the results may not be accurate.
- *
- * @method currentTime
- * @return {Number} currentTime of the soundFile in seconds.
- */
- p5.SoundFile.prototype.currentTime = function () {
- // TO DO --> make reverse() flip these values appropriately
- if (this._pauseTime > 0) {
- return this._pauseTime;
- } else {
- return this._lastPos / ac.sampleRate;
- }
- };
- /**
- * Move the playhead of the song to a position, in seconds. Start
- * and Stop time. If none are given, will reset the file to play
- * entire duration from start to finish.
- *
- * @method jump
- * @param {Number} cueTime cueTime of the soundFile in seconds.
- * @param {Number} duration duration in seconds.
- */
- p5.SoundFile.prototype.jump = function (cueTime, duration) {
- if (cueTime < 0 || cueTime > this.buffer.duration) {
- throw 'jump time out of range';
- }
- if (duration > this.buffer.duration - cueTime) {
- throw 'end time out of range';
- }
- var cTime = cueTime || 0;
- var eTime = duration || this.buffer.duration - cueTime;
- if (this.isPlaying()) {
- this.stop();
- }
- this.play(0, this.playbackRate, this.output.gain.value, cTime, eTime);
- };
- /**
- * Return the number of channels in a sound file.
- * For example, Mono = 1, Stereo = 2.
- *
- * @method channels
- * @return {Number} [channels]
- */
- p5.SoundFile.prototype.channels = function () {
- return this.buffer.numberOfChannels;
- };
- /**
- * Return the sample rate of the sound file.
- *
- * @method sampleRate
- * @return {Number} [sampleRate]
- */
- p5.SoundFile.prototype.sampleRate = function () {
- return this.buffer.sampleRate;
- };
- /**
- * Return the number of samples in a sound file.
- * Equal to sampleRate * duration.
- *
- * @method frames
- * @return {Number} [sampleCount]
- */
- p5.SoundFile.prototype.frames = function () {
- return this.buffer.length;
- };
- /**
- * Returns an array of amplitude peaks in a p5.SoundFile that can be
- * used to draw a static waveform. Scans through the p5.SoundFile's
- * audio buffer to find the greatest amplitudes. Accepts one
- * parameter, 'length', which determines size of the array.
- * Larger arrays result in more precise waveform visualizations.
- *
- * Inspired by Wavesurfer.js.
- *
- * @method getPeaks
- * @params {Number} [length] length is the size of the returned array.
- * Larger length results in more precision.
- * Defaults to 5*width of the browser window.
- * @returns {Float32Array} Array of peaks.
- */
- p5.SoundFile.prototype.getPeaks = function (length) {
- if (this.buffer) {
- // set length to window's width if no length is provided
- if (!length) {
- length = window.width * 5;
- }
- if (this.buffer) {
- var buffer = this.buffer;
- var sampleSize = buffer.length / length;
- var sampleStep = ~~(sampleSize / 10) || 1;
- var channels = buffer.numberOfChannels;
- var peaks = new Float32Array(Math.round(length));
- for (var c = 0; c < channels; c++) {
- var chan = buffer.getChannelData(c);
- for (var i = 0; i < length; i++) {
- var start = ~~(i * sampleSize);
- var end = ~~(start + sampleSize);
- var max = 0;
- for (var j = start; j < end; j += sampleStep) {
- var value = chan[j];
- if (value > max) {
- max = value;
- } else if (-value > max) {
- max = value;
- }
- }
- if (c === 0 || Math.abs(max) > peaks[i]) {
- peaks[i] = max;
- }
- }
- }
- return peaks;
- }
- } else {
- throw 'Cannot load peaks yet, buffer is not loaded';
- }
- };
- /**
- * Reverses the p5.SoundFile's buffer source.
- * Playback must be handled separately (see example).
- *
- * @method reverseBuffer
- * @example
- * <div><code>
- * var drum;
- *
- * function preload() {
- * drum = loadSound('assets/drum.mp3');
- * }
- *
- * function setup() {
- * drum.reverseBuffer();
- * drum.play();
- * }
- *
- * </code>
- * </div>
- */
- p5.SoundFile.prototype.reverseBuffer = function () {
- var curVol = this.getVolume();
- this.setVolume(0, 0.01, 0);
- this.pause();
- if (this.buffer) {
- for (var i = 0; i < this.buffer.numberOfChannels; i++) {
- Array.prototype.reverse.call(this.buffer.getChannelData(i));
- }
- // set reversed flag
- this.reversed = !this.reversed;
- } else {
- throw 'SoundFile is not done loading';
- }
- this.setVolume(curVol, 0.01, 0.0101);
- this.play();
- };
- /**
- * Schedule an event to be called when the soundfile
- * reaches the end of a buffer. If the soundfile is
- * playing through once, this will be called when it
- * ends. If it is looping, it will be called when
- * stop is called.
- *
- * @method onended
- * @param {Function} callback function to call when the
- * soundfile has ended.
- */
- p5.SoundFile.prototype.onended = function (callback) {
- this._onended = callback;
- return this;
- };
- p5.SoundFile.prototype.add = function () {
- };
- p5.SoundFile.prototype.dispose = function () {
- var now = p5sound.audiocontext.currentTime;
- // remove reference to soundfile
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- this.stop(now);
- if (this.buffer && this.bufferSourceNode) {
- for (var i = 0; i < this.bufferSourceNodes.length - 1; i++) {
- if (this.bufferSourceNodes[i] !== null) {
- this.bufferSourceNodes[i].disconnect();
- try {
- this.bufferSourceNodes[i].stop(now);
- } catch (e) {
- }
- this.bufferSourceNodes[i] = null;
- }
- }
- if (this.isPlaying()) {
- try {
- this._counterNode.stop(now);
- } catch (e) {
- console.log(e);
- }
- this._counterNode = null;
- }
- }
- if (this.output) {
- this.output.disconnect();
- this.output = null;
- }
- if (this.panner) {
- this.panner.disconnect();
- this.panner = null;
- }
- };
- /**
- * Connects the output of a p5sound object to input of another
- * p5.sound object. For example, you may connect a p5.SoundFile to an
- * FFT or an Effect. If no parameter is given, it will connect to
- * the master output. Most p5sound objects connect to the master
- * output when they are created.
- *
- * @method connect
- * @param {Object} [object] Audio object that accepts an input
- */
- p5.SoundFile.prototype.connect = function (unit) {
- if (!unit) {
- this.panner.connect(p5sound.input);
- } else {
- if (unit.hasOwnProperty('input')) {
- this.panner.connect(unit.input);
- } else {
- this.panner.connect(unit);
- }
- }
- };
- /**
- * Disconnects the output of this p5sound object.
- *
- * @method disconnect
- */
- p5.SoundFile.prototype.disconnect = function () {
- this.panner.disconnect();
- };
- /**
- */
- p5.SoundFile.prototype.getLevel = function (smoothing) {
- console.warn('p5.SoundFile.getLevel has been removed from the library. Use p5.Amplitude instead');
- };
- /**
- * Reset the source for this SoundFile to a
- * new path (URL).
- *
- * @method setPath
- * @param {String} path path to audio file
- * @param {Function} callback Callback
- */
- p5.SoundFile.prototype.setPath = function (p, callback) {
- var path = p5.prototype._checkFileFormats(p);
- this.url = path;
- this.load(callback);
- };
- /**
- * Replace the current Audio Buffer with a new Buffer.
- *
- * @param {Array} buf Array of Float32 Array(s). 2 Float32 Arrays
- * will create a stereo source. 1 will create
- * a mono source.
- */
- p5.SoundFile.prototype.setBuffer = function (buf) {
- var numChannels = buf.length;
- var size = buf[0].length;
- var newBuffer = ac.createBuffer(numChannels, size, ac.sampleRate);
- if (!buf[0] instanceof Float32Array) {
- buf[0] = new Float32Array(buf[0]);
- }
- for (var channelNum = 0; channelNum < numChannels; channelNum++) {
- var channel = newBuffer.getChannelData(channelNum);
- channel.set(buf[channelNum]);
- }
- this.buffer = newBuffer;
- // set numbers of channels on input to the panner
- this.panner.inputChannels(numChannels);
- };
- //////////////////////////////////////////////////
- // script processor node with an empty buffer to help
- // keep a sample-accurate position in playback buffer.
- // Inspired by Chinmay Pendharkar's technique for Sonoport --> http://bit.ly/1HwdCsV
- // Copyright [2015] [Sonoport (Asia) Pte. Ltd.],
- // Licensed under the Apache License http://apache.org/licenses/LICENSE-2.0
- ////////////////////////////////////////////////////////////////////////////////////
- // initialize counterNode, set its initial buffer and playbackRate
- p5.SoundFile.prototype._initCounterNode = function () {
- var self = this;
- var now = ac.currentTime;
- var cNode = ac.createBufferSource();
- // dispose of scope node if it already exists
- if (self._scopeNode) {
- self._scopeNode.disconnect();
- self._scopeNode.onaudioprocess = undefined;
- self._scopeNode = null;
- }
- self._scopeNode = ac.createScriptProcessor(256, 1, 1);
- // create counter buffer of the same length as self.buffer
- cNode.buffer = _createCounterBuffer(self.buffer);
- cNode.playbackRate.setValueAtTime(self.playbackRate, now);
- cNode.connect(self._scopeNode);
- self._scopeNode.connect(p5.soundOut._silentNode);
- self._scopeNode.onaudioprocess = function (processEvent) {
- var inputBuffer = processEvent.inputBuffer.getChannelData(0);
- // update the lastPos
- self._lastPos = inputBuffer[inputBuffer.length - 1] || 0;
- // do any callbacks that have been scheduled
- self._onTimeUpdate(self._lastPos);
- };
- return cNode;
- };
- // initialize sourceNode, set its initial buffer and playbackRate
- p5.SoundFile.prototype._initSourceNode = function () {
- var self = this;
- var now = ac.currentTime;
- var bufferSourceNode = ac.createBufferSource();
- bufferSourceNode.buffer = self.buffer;
- bufferSourceNode.playbackRate.value = self.playbackRate;
- return bufferSourceNode;
- };
- var _createCounterBuffer = function (buffer) {
- var array = new Float32Array(buffer.length);
- var audioBuf = ac.createBuffer(1, buffer.length, 44100);
- for (var index = 0; index < buffer.length; index++) {
- array[index] = index;
- }
- audioBuf.getChannelData(0).set(array);
- return audioBuf;
- };
- /**
- * processPeaks returns an array of timestamps where it thinks there is a beat.
- *
- * This is an asynchronous function that processes the soundfile in an offline audio context,
- * and sends the results to your callback function.
- *
- * The process involves running the soundfile through a lowpass filter, and finding all of the
- * peaks above the initial threshold. If the total number of peaks are below the minimum number of peaks,
- * it decreases the threshold and re-runs the analysis until either minPeaks or minThreshold are reached.
- *
- * @method processPeaks
- * @param {Function} callback a function to call once this data is returned
- * @param {Number} [initThreshold] initial threshold defaults to 0.9
- * @param {Number} [minThreshold] minimum threshold defaults to 0.22
- * @param {Number} [minPeaks] minimum number of peaks defaults to 200
- * @return {Array} Array of timestamped peaks
- */
- p5.SoundFile.prototype.processPeaks = function (callback, _initThreshold, _minThreshold, _minPeaks) {
- var bufLen = this.buffer.length;
- var sampleRate = this.buffer.sampleRate;
- var buffer = this.buffer;
- var initialThreshold = _initThreshold || 0.9, threshold = initialThreshold, minThreshold = _minThreshold || 0.22, minPeaks = _minPeaks || 200;
- // Create offline context
- var offlineContext = new OfflineAudioContext(1, bufLen, sampleRate);
- // create buffer source
- var source = offlineContext.createBufferSource();
- source.buffer = buffer;
- // Create filter. TO DO: allow custom setting of filter
- var filter = offlineContext.createBiquadFilter();
- filter.type = 'lowpass';
- source.connect(filter);
- filter.connect(offlineContext.destination);
- // start playing at time:0
- source.start(0);
- offlineContext.startRendering();
- // Render the song
- // act on the result
- offlineContext.oncomplete = function (e) {
- var data = {};
- var filteredBuffer = e.renderedBuffer;
- var bufferData = filteredBuffer.getChannelData(0);
- // step 1:
- // create Peak instances, add them to array, with strength and sampleIndex
- do {
- allPeaks = getPeaksAtThreshold(bufferData, threshold);
- threshold -= 0.005;
- } while (Object.keys(allPeaks).length < minPeaks && threshold >= minThreshold);
- // step 2:
- // find intervals for each peak in the sampleIndex, add tempos array
- var intervalCounts = countIntervalsBetweenNearbyPeaks(allPeaks);
- // step 3: find top tempos
- var groups = groupNeighborsByTempo(intervalCounts, filteredBuffer.sampleRate);
- // sort top intervals
- var topTempos = groups.sort(function (intA, intB) {
- return intB.count - intA.count;
- }).splice(0, 5);
- // set this SoundFile's tempo to the top tempo ??
- this.tempo = topTempos[0].tempo;
- // step 4:
- // new array of peaks at top tempo within a bpmVariance
- var bpmVariance = 5;
- var tempoPeaks = getPeaksAtTopTempo(allPeaks, topTempos[0].tempo, filteredBuffer.sampleRate, bpmVariance);
- callback(tempoPeaks);
- };
- };
- // process peaks
- var Peak = function (amp, i) {
- this.sampleIndex = i;
- this.amplitude = amp;
- this.tempos = [];
- this.intervals = [];
- };
- var allPeaks = [];
- // 1. for processPeaks() Function to identify peaks above a threshold
- // returns an array of peak indexes as frames (samples) of the original soundfile
- function getPeaksAtThreshold(data, threshold) {
- var peaksObj = {};
- var length = data.length;
- for (var i = 0; i < length; i++) {
- if (data[i] > threshold) {
- var amp = data[i];
- var peak = new Peak(amp, i);
- peaksObj[i] = peak;
- // Skip forward ~ 1/8s to get past this peak.
- i += 6000;
- }
- i++;
- }
- return peaksObj;
- }
- // 2. for processPeaks()
- function countIntervalsBetweenNearbyPeaks(peaksObj) {
- var intervalCounts = [];
- var peaksArray = Object.keys(peaksObj).sort();
- for (var index = 0; index < peaksArray.length; index++) {
- // find intervals in comparison to nearby peaks
- for (var i = 0; i < 10; i++) {
- var startPeak = peaksObj[peaksArray[index]];
- var endPeak = peaksObj[peaksArray[index + i]];
- if (startPeak && endPeak) {
- var startPos = startPeak.sampleIndex;
- var endPos = endPeak.sampleIndex;
- var interval = endPos - startPos;
- // add a sample interval to the startPeek in the allPeaks array
- if (interval > 0) {
- startPeak.intervals.push(interval);
- }
- // tally the intervals and return interval counts
- var foundInterval = intervalCounts.some(function (intervalCount, p) {
- if (intervalCount.interval === interval) {
- intervalCount.count++;
- return intervalCount;
- }
- });
- // store with JSON like formatting
- if (!foundInterval) {
- intervalCounts.push({
- interval: interval,
- count: 1
- });
- }
- }
- }
- }
- return intervalCounts;
- }
- // 3. for processPeaks --> find tempo
- function groupNeighborsByTempo(intervalCounts, sampleRate) {
- var tempoCounts = [];
- intervalCounts.forEach(function (intervalCount, i) {
- try {
- // Convert an interval to tempo
- var theoreticalTempo = Math.abs(60 / (intervalCount.interval / sampleRate));
- theoreticalTempo = mapTempo(theoreticalTempo);
- var foundTempo = tempoCounts.some(function (tempoCount) {
- if (tempoCount.tempo === theoreticalTempo)
- return tempoCount.count += intervalCount.count;
- });
- if (!foundTempo) {
- if (isNaN(theoreticalTempo)) {
- return;
- }
- tempoCounts.push({
- tempo: Math.round(theoreticalTempo),
- count: intervalCount.count
- });
- }
- } catch (e) {
- throw e;
- }
- });
- return tempoCounts;
- }
- // 4. for processPeaks - get peaks at top tempo
- function getPeaksAtTopTempo(peaksObj, tempo, sampleRate, bpmVariance) {
- var peaksAtTopTempo = [];
- var peaksArray = Object.keys(peaksObj).sort();
- // TO DO: filter out peaks that have the tempo and return
- for (var i = 0; i < peaksArray.length; i++) {
- var key = peaksArray[i];
- var peak = peaksObj[key];
- for (var j = 0; j < peak.intervals.length; j++) {
- var intervalBPM = Math.round(Math.abs(60 / (peak.intervals[j] / sampleRate)));
- intervalBPM = mapTempo(intervalBPM);
- var dif = intervalBPM - tempo;
- if (Math.abs(intervalBPM - tempo) < bpmVariance) {
- // convert sampleIndex to seconds
- peaksAtTopTempo.push(peak.sampleIndex / 44100);
- }
- }
- }
- // filter out peaks that are very close to each other
- peaksAtTopTempo = peaksAtTopTempo.filter(function (peakTime, index, arr) {
- var dif = arr[index + 1] - peakTime;
- if (dif > 0.01) {
- return true;
- }
- });
- return peaksAtTopTempo;
- }
- // helper function for processPeaks
- function mapTempo(theoreticalTempo) {
- // these scenarios create infinite while loop
- if (!isFinite(theoreticalTempo) || theoreticalTempo == 0) {
- return;
- }
- // Adjust the tempo to fit within the 90-180 BPM range
- while (theoreticalTempo < 90)
- theoreticalTempo *= 2;
- while (theoreticalTempo > 180 && theoreticalTempo > 90)
- theoreticalTempo /= 2;
- return theoreticalTempo;
- }
- /*** SCHEDULE EVENTS ***/
- /**
- * Schedule events to trigger every time a MediaElement
- * (audio/video) reaches a playback cue point.
- *
- * Accepts a callback function, a time (in seconds) at which to trigger
- * the callback, and an optional parameter for the callback.
- *
- * Time will be passed as the first parameter to the callback function,
- * and param will be the second parameter.
- *
- *
- * @method addCue
- * @param {Number} time Time in seconds, relative to this media
- * element's playback. For example, to trigger
- * an event every time playback reaches two
- * seconds, pass in the number 2. This will be
- * passed as the first parameter to
- * the callback function.
- * @param {Function} callback Name of a function that will be
- * called at the given time. The callback will
- * receive time and (optionally) param as its
- * two parameters.
- * @param {Object} [value] An object to be passed as the
- * second parameter to the
- * callback function.
- * @return {Number} id ID of this cue,
- * useful for removeCue(id)
- * @example
- * <div><code>
- * function setup() {
- * background(0);
- * noStroke();
- * fill(255);
- * textAlign(CENTER);
- * text('click to play', width/2, height/2);
- *
- * mySound = loadSound('assets/beat.mp3');
- *
- * // schedule calls to changeText
- * mySound.addCue(0.50, changeText, "hello" );
- * mySound.addCue(1.00, changeText, "p5" );
- * mySound.addCue(1.50, changeText, "what" );
- * mySound.addCue(2.00, changeText, "do" );
- * mySound.addCue(2.50, changeText, "you" );
- * mySound.addCue(3.00, changeText, "want" );
- * mySound.addCue(4.00, changeText, "to" );
- * mySound.addCue(5.00, changeText, "make" );
- * mySound.addCue(6.00, changeText, "?" );
- * }
- *
- * function changeText(val) {
- * background(0);
- * text(val, width/2, height/2);
- * }
- *
- * function mouseClicked() {
- * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
- * if (mySound.isPlaying() ) {
- * mySound.stop();
- * } else {
- * mySound.play();
- * }
- * }
- * }
- * </code></div>
- */
- p5.SoundFile.prototype.addCue = function (time, callback, val) {
- var id = this._cueIDCounter++;
- var cue = new Cue(callback, time, id, val);
- this._cues.push(cue);
- // if (!this.elt.ontimeupdate) {
- // this.elt.ontimeupdate = this._onTimeUpdate.bind(this);
- // }
- return id;
- };
- /**
- * Remove a callback based on its ID. The ID is returned by the
- * addCue method.
- *
- * @method removeCue
- * @param {Number} id ID of the cue, as returned by addCue
- */
- p5.SoundFile.prototype.removeCue = function (id) {
- var cueLength = this._cues.length;
- for (var i = 0; i < cueLength; i++) {
- var cue = this._cues[i];
- if (cue.id === id) {
- this.cues.splice(i, 1);
- }
- }
- if (this._cues.length === 0) {
- }
- };
- /**
- * Remove all of the callbacks that had originally been scheduled
- * via the addCue method.
- *
- * @method clearCues
- */
- p5.SoundFile.prototype.clearCues = function () {
- this._cues = [];
- };
- // private method that checks for cues to be fired if events
- // have been scheduled using addCue(callback, time).
- p5.SoundFile.prototype._onTimeUpdate = function (position) {
- var playbackTime = position / this.buffer.sampleRate;
- var cueLength = this._cues.length;
- for (var i = 0; i < cueLength; i++) {
- var cue = this._cues[i];
- var callbackTime = cue.time;
- var val = cue.val;
- if (this._prevTime < callbackTime && callbackTime <= playbackTime) {
- // pass the scheduled callbackTime as parameter to the callback
- cue.callback(val);
- }
- }
- this._prevTime = playbackTime;
- };
- // Cue inspired by JavaScript setTimeout, and the
- // Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org
- var Cue = function (callback, time, id, val) {
- this.callback = callback;
- this.time = time;
- this.id = id;
- this.val = val;
- };
- }(sndcore, errorHandler, master);
- var amplitude;
- amplitude = function () {
- 'use strict';
- var p5sound = master;
- /**
- * Amplitude measures volume between 0.0 and 1.0.
- * Listens to all p5sound by default, or use setInput()
- * to listen to a specific sound source. Accepts an optional
- * smoothing value, which defaults to 0.
- *
- * @class p5.Amplitude
- * @constructor
- * @param {Number} [smoothing] between 0.0 and .999 to smooth
- * amplitude readings (defaults to 0)
- * @return {Object} Amplitude Object
- * @example
- * <div><code>
- * var sound, amplitude, cnv;
- *
- * function preload(){
- * sound = loadSound('assets/beat.mp3');
- * }
- * function setup() {
- * cnv = createCanvas(100,100);
- * amplitude = new p5.Amplitude();
- *
- * // start / stop the sound when canvas is clicked
- * cnv.mouseClicked(function() {
- * if (sound.isPlaying() ){
- * sound.stop();
- * } else {
- * sound.play();
- * }
- * });
- * }
- * function draw() {
- * background(0);
- * fill(255);
- * var level = amplitude.getLevel();
- * var size = map(level, 0, 1, 0, 200);
- * ellipse(width/2, height/2, size, size);
- * }
- *
- * </code></div>
- */
- p5.Amplitude = function (smoothing) {
- // Set to 2048 for now. In future iterations, this should be inherited or parsed from p5sound's default
- this.bufferSize = 2048;
- // set audio context
- this.audiocontext = p5sound.audiocontext;
- this.processor = this.audiocontext.createScriptProcessor(this.bufferSize, 2, 1);
- // for connections
- this.input = this.processor;
- this.output = this.audiocontext.createGain();
- // smoothing defaults to 0
- this.smoothing = smoothing || 0;
- // the variables to return
- this.volume = 0;
- this.average = 0;
- this.stereoVol = [
- 0,
- 0
- ];
- this.stereoAvg = [
- 0,
- 0
- ];
- this.stereoVolNorm = [
- 0,
- 0
- ];
- this.volMax = 0.001;
- this.normalize = false;
- this.processor.onaudioprocess = this._audioProcess.bind(this);
- this.processor.connect(this.output);
- this.output.gain.value = 0;
- // this may only be necessary because of a Chrome bug
- this.output.connect(this.audiocontext.destination);
- // connect to p5sound master output by default, unless set by input()
- p5sound.meter.connect(this.processor);
- // add this p5.SoundFile to the soundArray
- p5sound.soundArray.push(this);
- };
- /**
- * Connects to the p5sound instance (master output) by default.
- * Optionally, you can pass in a specific source (i.e. a soundfile).
- *
- * @method setInput
- * @param {soundObject|undefined} [snd] set the sound source
- * (optional, defaults to
- * master output)
- * @param {Number|undefined} [smoothing] a range between 0.0 and 1.0
- * to smooth amplitude readings
- * @example
- * <div><code>
- * function preload(){
- * sound1 = loadSound('assets/beat.mp3');
- * sound2 = loadSound('assets/drum.mp3');
- * }
- * function setup(){
- * amplitude = new p5.Amplitude();
- * sound1.play();
- * sound2.play();
- * amplitude.setInput(sound2);
- * }
- * function draw() {
- * background(0);
- * fill(255);
- * var level = amplitude.getLevel();
- * var size = map(level, 0, 1, 0, 200);
- * ellipse(width/2, height/2, size, size);
- * }
- * function mouseClicked(){
- * sound1.stop();
- * sound2.stop();
- * }
- * </code></div>
- */
- p5.Amplitude.prototype.setInput = function (source, smoothing) {
- p5sound.meter.disconnect();
- if (smoothing) {
- this.smoothing = smoothing;
- }
- // connect to the master out of p5s instance if no snd is provided
- if (source == null) {
- console.log('Amplitude input source is not ready! Connecting to master output instead');
- p5sound.meter.connect(this.processor);
- } else if (source instanceof p5.Signal) {
- source.output.connect(this.processor);
- } else if (source) {
- source.connect(this.processor);
- this.processor.disconnect();
- this.processor.connect(this.output);
- } else {
- p5sound.meter.connect(this.processor);
- }
- };
- p5.Amplitude.prototype.connect = function (unit) {
- if (unit) {
- if (unit.hasOwnProperty('input')) {
- this.output.connect(unit.input);
- } else {
- this.output.connect(unit);
- }
- } else {
- this.output.connect(this.panner.connect(p5sound.input));
- }
- };
- p5.Amplitude.prototype.disconnect = function (unit) {
- this.output.disconnect();
- };
- // TO DO make this stereo / dependent on # of audio channels
- p5.Amplitude.prototype._audioProcess = function (event) {
- for (var channel = 0; channel < event.inputBuffer.numberOfChannels; channel++) {
- var inputBuffer = event.inputBuffer.getChannelData(channel);
- var bufLength = inputBuffer.length;
- var total = 0;
- var sum = 0;
- var x;
- for (var i = 0; i < bufLength; i++) {
- x = inputBuffer[i];
- if (this.normalize) {
- total += Math.max(Math.min(x / this.volMax, 1), -1);
- sum += Math.max(Math.min(x / this.volMax, 1), -1) * Math.max(Math.min(x / this.volMax, 1), -1);
- } else {
- total += x;
- sum += x * x;
- }
- }
- var average = total / bufLength;
- // ... then take the square root of the sum.
- var rms = Math.sqrt(sum / bufLength);
- this.stereoVol[channel] = Math.max(rms, this.stereoVol[channel] * this.smoothing);
- this.stereoAvg[channel] = Math.max(average, this.stereoVol[channel] * this.smoothing);
- this.volMax = Math.max(this.stereoVol[channel], this.volMax);
- }
- // add volume from all channels together
- var self = this;
- var volSum = this.stereoVol.reduce(function (previousValue, currentValue, index) {
- self.stereoVolNorm[index - 1] = Math.max(Math.min(self.stereoVol[index - 1] / self.volMax, 1), 0);
- self.stereoVolNorm[index] = Math.max(Math.min(self.stereoVol[index] / self.volMax, 1), 0);
- return previousValue + currentValue;
- });
- // volume is average of channels
- this.volume = volSum / this.stereoVol.length;
- // normalized value
- this.volNorm = Math.max(Math.min(this.volume / this.volMax, 1), 0);
- };
- /**
- * Returns a single Amplitude reading at the moment it is called.
- * For continuous readings, run in the draw loop.
- *
- * @method getLevel
- * @param {Number} [channel] Optionally return only channel 0 (left) or 1 (right)
- * @return {Number} Amplitude as a number between 0.0 and 1.0
- * @example
- * <div><code>
- * function preload(){
- * sound = loadSound('assets/beat.mp3');
- * }
- * function setup() {
- * amplitude = new p5.Amplitude();
- * sound.play();
- * }
- * function draw() {
- * background(0);
- * fill(255);
- * var level = amplitude.getLevel();
- * var size = map(level, 0, 1, 0, 200);
- * ellipse(width/2, height/2, size, size);
- * }
- * function mouseClicked(){
- * sound.stop();
- * }
- * </code></div>
- */
- p5.Amplitude.prototype.getLevel = function (channel) {
- if (typeof channel !== 'undefined') {
- if (this.normalize) {
- return this.stereoVolNorm[channel];
- } else {
- return this.stereoVol[channel];
- }
- } else if (this.normalize) {
- return this.volNorm;
- } else {
- return this.volume;
- }
- };
- /**
- * Determines whether the results of Amplitude.process() will be
- * Normalized. To normalize, Amplitude finds the difference the
- * loudest reading it has processed and the maximum amplitude of
- * 1.0. Amplitude adds this difference to all values to produce
- * results that will reliably map between 0.0 and 1.0. However,
- * if a louder moment occurs, the amount that Normalize adds to
- * all the values will change. Accepts an optional boolean parameter
- * (true or false). Normalizing is off by default.
- *
- * @method toggleNormalize
- * @param {boolean} [boolean] set normalize to true (1) or false (0)
- */
- p5.Amplitude.prototype.toggleNormalize = function (bool) {
- if (typeof bool === 'boolean') {
- this.normalize = bool;
- } else {
- this.normalize = !this.normalize;
- }
- };
- /**
- * Smooth Amplitude analysis by averaging with the last analysis
- * frame. Off by default.
- *
- * @method smooth
- * @param {Number} set smoothing from 0.0 <= 1
- */
- p5.Amplitude.prototype.smooth = function (s) {
- if (s >= 0 && s < 1) {
- this.smoothing = s;
- } else {
- console.log('Error: smoothing must be between 0 and 1');
- }
- };
- p5.Amplitude.prototype.dispose = function () {
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- this.input.disconnect();
- this.output.disconnect();
- this.input = this.processor = undefined;
- this.output = undefined;
- };
- }(master);
- var fft;
- fft = function () {
- 'use strict';
- var p5sound = master;
- /**
- * <p>FFT (Fast Fourier Transform) is an analysis algorithm that
- * isolates individual
- * <a href="https://en.wikipedia.org/wiki/Audio_frequency">
- * audio frequencies</a> within a waveform.</p>
- *
- * <p>Once instantiated, a p5.FFT object can return an array based on
- * two types of analyses: <br> • <code>FFT.waveform()</code> computes
- * amplitude values along the time domain. The array indices correspond
- * to samples across a brief moment in time. Each value represents
- * amplitude of the waveform at that sample of time.<br>
- * • <code>FFT.analyze() </code> computes amplitude values along the
- * frequency domain. The array indices correspond to frequencies (i.e.
- * pitches), from the lowest to the highest that humans can hear. Each
- * value represents amplitude at that slice of the frequency spectrum.
- * Use with <code>getEnergy()</code> to measure amplitude at specific
- * frequencies, or within a range of frequencies. </p>
- *
- * <p>FFT analyzes a very short snapshot of sound called a sample
- * buffer. It returns an array of amplitude measurements, referred
- * to as <code>bins</code>. The array is 1024 bins long by default.
- * You can change the bin array length, but it must be a power of 2
- * between 16 and 1024 in order for the FFT algorithm to function
- * correctly. The actual size of the FFT buffer is twice the
- * number of bins, so given a standard sample rate, the buffer is
- * 2048/44100 seconds long.</p>
- *
- *
- * @class p5.FFT
- * @constructor
- * @param {Number} [smoothing] Smooth results of Freq Spectrum.
- * 0.0 < smoothing < 1.0.
- * Defaults to 0.8.
- * @param {Number} [bins] Length of resulting array.
- * Must be a power of two between
- * 16 and 1024. Defaults to 1024.
- * @return {Object} FFT Object
- * @example
- * <div><code>
- * function preload(){
- * sound = loadSound('assets/Damscray_DancingTiger.mp3');
- * }
- *
- * function setup(){
- * var cnv = createCanvas(100,100);
- * cnv.mouseClicked(togglePlay);
- * fft = new p5.FFT();
- * sound.amp(0.2);
- * }
- *
- * function draw(){
- * background(0);
- *
- * var spectrum = fft.analyze();
- * noStroke();
- * fill(0,255,0); // spectrum is green
- * for (var i = 0; i< spectrum.length; i++){
- * var x = map(i, 0, spectrum.length, 0, width);
- * var h = -height + map(spectrum[i], 0, 255, height, 0);
- * rect(x, height, width / spectrum.length, h )
- * }
- *
- * var waveform = fft.waveform();
- * noFill();
- * beginShape();
- * stroke(255,0,0); // waveform is red
- * strokeWeight(1);
- * for (var i = 0; i< waveform.length; i++){
- * var x = map(i, 0, waveform.length, 0, width);
- * var y = map( waveform[i], -1, 1, 0, height);
- * vertex(x,y);
- * }
- * endShape();
- *
- * text('click to play/pause', 4, 10);
- * }
- *
- * // fade sound if mouse is over canvas
- * function togglePlay() {
- * if (sound.isPlaying()) {
- * sound.pause();
- * } else {
- * sound.loop();
- * }
- * }
- * </code></div>
- */
- p5.FFT = function (smoothing, bins) {
- this.smoothing = smoothing || 0.8;
- this.bins = bins || 1024;
- var FFT_SIZE = bins * 2 || 2048;
- this.input = this.analyser = p5sound.audiocontext.createAnalyser();
- // default connections to p5sound fftMeter
- p5sound.fftMeter.connect(this.analyser);
- this.analyser.smoothingTimeConstant = this.smoothing;
- this.analyser.fftSize = FFT_SIZE;
- this.freqDomain = new Uint8Array(this.analyser.frequencyBinCount);
- this.timeDomain = new Uint8Array(this.analyser.frequencyBinCount);
- // predefined frequency ranages, these will be tweakable
- this.bass = [
- 20,
- 140
- ];
- this.lowMid = [
- 140,
- 400
- ];
- this.mid = [
- 400,
- 2600
- ];
- this.highMid = [
- 2600,
- 5200
- ];
- this.treble = [
- 5200,
- 14000
- ];
- // add this p5.SoundFile to the soundArray
- p5sound.soundArray.push(this);
- };
- /**
- * Set the input source for the FFT analysis. If no source is
- * provided, FFT will analyze all sound in the sketch.
- *
- * @method setInput
- * @param {Object} [source] p5.sound object (or web audio API source node)
- */
- p5.FFT.prototype.setInput = function (source) {
- if (!source) {
- p5sound.fftMeter.connect(this.analyser);
- } else {
- if (source.output) {
- source.output.connect(this.analyser);
- } else if (source.connect) {
- source.connect(this.analyser);
- }
- p5sound.fftMeter.disconnect();
- }
- };
- /**
- * Returns an array of amplitude values (between -1.0 and +1.0) that represent
- * a snapshot of amplitude readings in a single buffer. Length will be
- * equal to bins (defaults to 1024). Can be used to draw the waveform
- * of a sound.
- *
- * @method waveform
- * @param {Number} [bins] Must be a power of two between
- * 16 and 1024. Defaults to 1024.
- * @param {String} [precision] If any value is provided, will return results
- * in a Float32 Array which is more precise
- * than a regular array.
- * @return {Array} Array Array of amplitude values (-1 to 1)
- * over time. Array length = bins.
- *
- */
- p5.FFT.prototype.waveform = function () {
- var bins, mode, normalArray;
- for (var i = 0; i < arguments.length; i++) {
- if (typeof arguments[i] === 'number') {
- bins = arguments[i];
- this.analyser.fftSize = bins * 2;
- }
- if (typeof arguments[i] === 'string') {
- mode = arguments[i];
- }
- }
- // getFloatFrequencyData doesnt work in Safari as of 5/2015
- if (mode && !p5.prototype._isSafari()) {
- timeToFloat(this, this.timeDomain);
- this.analyser.getFloatTimeDomainData(this.timeDomain);
- return this.timeDomain;
- } else {
- timeToInt(this, this.timeDomain);
- this.analyser.getByteTimeDomainData(this.timeDomain);
- var normalArray = new Array();
- for (var i = 0; i < this.timeDomain.length; i++) {
- var scaled = p5.prototype.map(this.timeDomain[i], 0, 255, -1, 1);
- normalArray.push(scaled);
- }
- return normalArray;
- }
- };
- /**
- * Returns an array of amplitude values (between 0 and 255)
- * across the frequency spectrum. Length is equal to FFT bins
- * (1024 by default). The array indices correspond to frequencies
- * (i.e. pitches), from the lowest to the highest that humans can
- * hear. Each value represents amplitude at that slice of the
- * frequency spectrum. Must be called prior to using
- * <code>getEnergy()</code>.
- *
- * @method analyze
- * @param {Number} [bins] Must be a power of two between
- * 16 and 1024. Defaults to 1024.
- * @param {Number} [scale] If "dB," returns decibel
- * float measurements between
- * -140 and 0 (max).
- * Otherwise returns integers from 0-255.
- * @return {Array} spectrum Array of energy (amplitude/volume)
- * values across the frequency spectrum.
- * Lowest energy (silence) = 0, highest
- * possible is 255.
- * @example
- * <div><code>
- * var osc;
- * var fft;
- *
- * function setup(){
- * createCanvas(100,100);
- * osc = new p5.Oscillator();
- * osc.amp(0);
- * osc.start();
- * fft = new p5.FFT();
- * }
- *
- * function draw(){
- * background(0);
- *
- * var freq = map(mouseX, 0, 800, 20, 15000);
- * freq = constrain(freq, 1, 20000);
- * osc.freq(freq);
- *
- * var spectrum = fft.analyze();
- * noStroke();
- * fill(0,255,0); // spectrum is green
- * for (var i = 0; i< spectrum.length; i++){
- * var x = map(i, 0, spectrum.length, 0, width);
- * var h = -height + map(spectrum[i], 0, 255, height, 0);
- * rect(x, height, width / spectrum.length, h );
- * }
- *
- * stroke(255);
- * text('Freq: ' + round(freq)+'Hz', 10, 10);
- *
- * isMouseOverCanvas();
- * }
- *
- * // only play sound when mouse is over canvas
- * function isMouseOverCanvas() {
- * var mX = mouseX, mY = mouseY;
- * if (mX > 0 && mX < width && mY < height && mY > 0) {
- * osc.amp(0.5, 0.2);
- * } else {
- * osc.amp(0, 0.2);
- * }
- * }
- * </code></div>
- *
- *
- */
- p5.FFT.prototype.analyze = function () {
- var bins, mode;
- for (var i = 0; i < arguments.length; i++) {
- if (typeof arguments[i] === 'number') {
- bins = this.bins = arguments[i];
- this.analyser.fftSize = this.bins * 2;
- }
- if (typeof arguments[i] === 'string') {
- mode = arguments[i];
- }
- }
- if (mode && mode.toLowerCase() === 'db') {
- freqToFloat(this);
- this.analyser.getFloatFrequencyData(this.freqDomain);
- return this.freqDomain;
- } else {
- freqToInt(this, this.freqDomain);
- this.analyser.getByteFrequencyData(this.freqDomain);
- var normalArray = Array.apply([], this.freqDomain);
- normalArray.length === this.analyser.fftSize;
- normalArray.constructor === Array;
- return normalArray;
- }
- };
- /**
- * Returns the amount of energy (volume) at a specific
- * <a href="en.wikipedia.org/wiki/Audio_frequency" target="_blank">
- * frequency</a>, or the average amount of energy between two
- * frequencies. Accepts Number(s) corresponding
- * to frequency (in Hz), or a String corresponding to predefined
- * frequency ranges ("bass", "lowMid", "mid", "highMid", "treble").
- * Returns a range between 0 (no energy/volume at that frequency) and
- * 255 (maximum energy).
- * <em>NOTE: analyze() must be called prior to getEnergy(). Analyze()
- * tells the FFT to analyze frequency data, and getEnergy() uses
- * the results determine the value at a specific frequency or
- * range of frequencies.</em></p>
- *
- * @method getEnergy
- * @param {Number|String} frequency1 Will return a value representing
- * energy at this frequency. Alternately,
- * the strings "bass", "lowMid" "mid",
- * "highMid", and "treble" will return
- * predefined frequency ranges.
- * @param {Number} [frequency2] If a second frequency is given,
- * will return average amount of
- * energy that exists between the
- * two frequencies.
- * @return {Number} Energy Energy (volume/amplitude) from
- * 0 and 255.
- *
- */
- p5.FFT.prototype.getEnergy = function (frequency1, frequency2) {
- var nyquist = p5sound.audiocontext.sampleRate / 2;
- if (frequency1 === 'bass') {
- frequency1 = this.bass[0];
- frequency2 = this.bass[1];
- } else if (frequency1 === 'lowMid') {
- frequency1 = this.lowMid[0];
- frequency2 = this.lowMid[1];
- } else if (frequency1 === 'mid') {
- frequency1 = this.mid[0];
- frequency2 = this.mid[1];
- } else if (frequency1 === 'highMid') {
- frequency1 = this.highMid[0];
- frequency2 = this.highMid[1];
- } else if (frequency1 === 'treble') {
- frequency1 = this.treble[0];
- frequency2 = this.treble[1];
- }
- if (typeof frequency1 !== 'number') {
- throw 'invalid input for getEnergy()';
- } else if (!frequency2) {
- var index = Math.round(frequency1 / nyquist * this.freqDomain.length);
- return this.freqDomain[index];
- } else if (frequency1 && frequency2) {
- // if second is higher than first
- if (frequency1 > frequency2) {
- var swap = frequency2;
- frequency2 = frequency1;
- frequency1 = swap;
- }
- var lowIndex = Math.round(frequency1 / nyquist * this.freqDomain.length);
- var highIndex = Math.round(frequency2 / nyquist * this.freqDomain.length);
- var total = 0;
- var numFrequencies = 0;
- // add up all of the values for the frequencies
- for (var i = lowIndex; i <= highIndex; i++) {
- total += this.freqDomain[i];
- numFrequencies += 1;
- }
- // divide by total number of frequencies
- var toReturn = total / numFrequencies;
- return toReturn;
- } else {
- throw 'invalid input for getEnergy()';
- }
- };
- // compatability with v.012, changed to getEnergy in v.0121. Will be deprecated...
- p5.FFT.prototype.getFreq = function (freq1, freq2) {
- console.log('getFreq() is deprecated. Please use getEnergy() instead.');
- var x = this.getEnergy(freq1, freq2);
- return x;
- };
- /**
- * Returns the
- * <a href="http://en.wikipedia.org/wiki/Spectral_centroid" target="_blank">
- * spectral centroid</a> of the input signal.
- * <em>NOTE: analyze() must be called prior to getCentroid(). Analyze()
- * tells the FFT to analyze frequency data, and getCentroid() uses
- * the results determine the spectral centroid.</em></p>
- *
- * @method getCentroid
- * @return {Number} Spectral Centroid Frequency Frequency of the spectral centroid in Hz.
- *
- *
- * @example
- * <div><code>
- *
- *
- *function setup(){
- * cnv = createCanvas(800,400);
- * sound = new p5.AudioIn();
- * sound.start();
- * fft = new p5.FFT();
- * sound.connect(fft);
- *}
- *
- *
- *function draw(){
- *
- * var centroidplot = 0.0;
- * var spectralCentroid = 0;
- *
- *
- * background(0);
- * stroke(0,255,0);
- * var spectrum = fft.analyze();
- * fill(0,255,0); // spectrum is green
- *
- * //draw the spectrum
- *
- * for (var i = 0; i< spectrum.length; i++){
- * var x = map(log(i), 0, log(spectrum.length), 0, width);
- * var h = map(spectrum[i], 0, 255, 0, height);
- * var rectangle_width = (log(i+1)-log(i))*(width/log(spectrum.length));
- * rect(x, height, rectangle_width, -h )
- * }
-
- * var nyquist = 22050;
- *
- * // get the centroid
- * spectralCentroid = fft.getCentroid();
- *
- * // the mean_freq_index calculation is for the display.
- * var mean_freq_index = spectralCentroid/(nyquist/spectrum.length);
- *
- * centroidplot = map(log(mean_freq_index), 0, log(spectrum.length), 0, width);
- *
- *
- * stroke(255,0,0); // the line showing where the centroid is will be red
- *
- * rect(centroidplot, 0, width / spectrum.length, height)
- * noStroke();
- * fill(255,255,255); // text is white
- * textSize(40);
- * text("centroid: "+round(spectralCentroid)+" Hz", 10, 40);
- *}
- * </code></div>
- */
- p5.FFT.prototype.getCentroid = function () {
- var nyquist = p5sound.audiocontext.sampleRate / 2;
- var cumulative_sum = 0;
- var centroid_normalization = 0;
- for (var i = 0; i < this.freqDomain.length; i++) {
- cumulative_sum += i * this.freqDomain[i];
- centroid_normalization += this.freqDomain[i];
- }
- var mean_freq_index = 0;
- if (centroid_normalization != 0) {
- mean_freq_index = cumulative_sum / centroid_normalization;
- }
- var spec_centroid_freq = mean_freq_index * (nyquist / this.freqDomain.length);
- return spec_centroid_freq;
- };
- /**
- * Smooth FFT analysis by averaging with the last analysis frame.
- *
- * @method smooth
- * @param {Number} smoothing 0.0 < smoothing < 1.0.
- * Defaults to 0.8.
- */
- p5.FFT.prototype.smooth = function (s) {
- if (s) {
- this.smoothing = s;
- }
- this.analyser.smoothingTimeConstant = s;
- };
- p5.FFT.prototype.dispose = function () {
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- this.analyser.disconnect();
- this.analyser = undefined;
- };
- /**
- * Returns an array of average amplitude values for a given number
- * of frequency bands split equally. N defaults to 16.
- * <em>NOTE: analyze() must be called prior to linAverages(). Analyze()
- * tells the FFT to analyze frequency data, and linAverages() uses
- * the results to group them into a smaller set of averages.</em></p>
- *
- * @method linAverages
- * @param {Number} N Number of returned frequency groups
- * @return {Array} linearAverages Array of average amplitude values for each group
- */
- p5.FFT.prototype.linAverages = function (N) {
- var N = N || 16;
- // This prevents undefined, null or 0 values of N
- var spectrum = this.freqDomain;
- var spectrumLength = spectrum.length;
- var spectrumStep = Math.floor(spectrumLength / N);
- var linearAverages = new Array(N);
- // Keep a second index for the current average group and place the values accordingly
- // with only one loop in the spectrum data
- var groupIndex = 0;
- for (var specIndex = 0; specIndex < spectrumLength; specIndex++) {
- linearAverages[groupIndex] = linearAverages[groupIndex] !== undefined ? (linearAverages[groupIndex] + spectrum[specIndex]) / 2 : spectrum[specIndex];
- // Increase the group index when the last element of the group is processed
- if (specIndex % spectrumStep == spectrumStep - 1) {
- groupIndex++;
- }
- }
- return linearAverages;
- };
- /**
- * Returns an array of average amplitude values of the spectrum, for a given
- * set of <a href="https://en.wikipedia.org/wiki/Octave_band" target="_blank">
- * Octave Bands</a>
- * <em>NOTE: analyze() must be called prior to logAverages(). Analyze()
- * tells the FFT to analyze frequency data, and logAverages() uses
- * the results to group them into a smaller set of averages.</em></p>
- *
- * @method logAverages
- * @param {Array} octaveBands Array of Octave Bands objects for grouping
- * @return {Array} logAverages Array of average amplitude values for each group
- */
- p5.FFT.prototype.logAverages = function (octaveBands) {
- var nyquist = p5sound.audiocontext.sampleRate / 2;
- var spectrum = this.freqDomain;
- var spectrumLength = spectrum.length;
- var logAverages = new Array(octaveBands.length);
- // Keep a second index for the current average group and place the values accordingly
- // With only one loop in the spectrum data
- var octaveIndex = 0;
- for (var specIndex = 0; specIndex < spectrumLength; specIndex++) {
- var specIndexFrequency = Math.round(specIndex * nyquist / this.freqDomain.length);
- // Increase the group index if the current frequency exceeds the limits of the band
- if (specIndexFrequency > octaveBands[octaveIndex].hi) {
- octaveIndex++;
- }
- logAverages[octaveIndex] = logAverages[octaveIndex] !== undefined ? (logAverages[octaveIndex] + spectrum[specIndex]) / 2 : spectrum[specIndex];
- }
- return logAverages;
- };
- /**
- * Calculates and Returns the 1/N
- * <a href="https://en.wikipedia.org/wiki/Octave_band" target="_blank">Octave Bands</a>
- * N defaults to 3 and minimum central frequency to 15.625Hz.
- * (1/3 Octave Bands ~= 31 Frequency Bands)
- * Setting fCtr0 to a central value of a higher octave will ignore the lower bands
- * and produce less frequency groups.
- *
- * @method getOctaveBands
- * @param {Number} N Specifies the 1/N type of generated octave bands
- * @param {Number} fCtr0 Minimum central frequency for the lowest band
- * @return {Array} octaveBands Array of octave band objects with their bounds
- */
- p5.FFT.prototype.getOctaveBands = function (N, fCtr0) {
- var N = N || 3;
- // Default to 1/3 Octave Bands
- var fCtr0 = fCtr0 || 15.625;
- // Minimum central frequency, defaults to 15.625Hz
- var octaveBands = [];
- var lastFrequencyBand = {
- lo: fCtr0 / Math.pow(2, 1 / (2 * N)),
- ctr: fCtr0,
- hi: fCtr0 * Math.pow(2, 1 / (2 * N))
- };
- octaveBands.push(lastFrequencyBand);
- var nyquist = p5sound.audiocontext.sampleRate / 2;
- while (lastFrequencyBand.hi < nyquist) {
- var newFrequencyBand = {};
- newFrequencyBand.lo = lastFrequencyBand.hi, newFrequencyBand.ctr = lastFrequencyBand.ctr * Math.pow(2, 1 / N), newFrequencyBand.hi = newFrequencyBand.ctr * Math.pow(2, 1 / (2 * N)), octaveBands.push(newFrequencyBand);
- lastFrequencyBand = newFrequencyBand;
- }
- return octaveBands;
- };
- // helper methods to convert type from float (dB) to int (0-255)
- var freqToFloat = function (fft) {
- if (fft.freqDomain instanceof Float32Array === false) {
- fft.freqDomain = new Float32Array(fft.analyser.frequencyBinCount);
- }
- };
- var freqToInt = function (fft) {
- if (fft.freqDomain instanceof Uint8Array === false) {
- fft.freqDomain = new Uint8Array(fft.analyser.frequencyBinCount);
- }
- };
- var timeToFloat = function (fft) {
- if (fft.timeDomain instanceof Float32Array === false) {
- fft.timeDomain = new Float32Array(fft.analyser.frequencyBinCount);
- }
- };
- var timeToInt = function (fft) {
- if (fft.timeDomain instanceof Uint8Array === false) {
- fft.timeDomain = new Uint8Array(fft.analyser.frequencyBinCount);
- }
- };
- }(master);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_core_Tone;
- Tone_core_Tone = function () {
- 'use strict';
- function isUndef(val) {
- return val === void 0;
- }
- function isFunction(val) {
- return typeof val === 'function';
- }
- var audioContext;
- if (isUndef(window.AudioContext)) {
- window.AudioContext = window.webkitAudioContext;
- }
- if (isUndef(window.OfflineAudioContext)) {
- window.OfflineAudioContext = window.webkitOfflineAudioContext;
- }
- if (!isUndef(AudioContext)) {
- audioContext = new AudioContext();
- } else {
- throw new Error('Web Audio is not supported in this browser');
- }
- if (!isFunction(AudioContext.prototype.createGain)) {
- AudioContext.prototype.createGain = AudioContext.prototype.createGainNode;
- }
- if (!isFunction(AudioContext.prototype.createDelay)) {
- AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode;
- }
- if (!isFunction(AudioContext.prototype.createPeriodicWave)) {
- AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable;
- }
- if (!isFunction(AudioBufferSourceNode.prototype.start)) {
- AudioBufferSourceNode.prototype.start = AudioBufferSourceNode.prototype.noteGrainOn;
- }
- if (!isFunction(AudioBufferSourceNode.prototype.stop)) {
- AudioBufferSourceNode.prototype.stop = AudioBufferSourceNode.prototype.noteOff;
- }
- if (!isFunction(OscillatorNode.prototype.start)) {
- OscillatorNode.prototype.start = OscillatorNode.prototype.noteOn;
- }
- if (!isFunction(OscillatorNode.prototype.stop)) {
- OscillatorNode.prototype.stop = OscillatorNode.prototype.noteOff;
- }
- if (!isFunction(OscillatorNode.prototype.setPeriodicWave)) {
- OscillatorNode.prototype.setPeriodicWave = OscillatorNode.prototype.setWaveTable;
- }
- AudioNode.prototype._nativeConnect = AudioNode.prototype.connect;
- AudioNode.prototype.connect = function (B, outNum, inNum) {
- if (B.input) {
- if (Array.isArray(B.input)) {
- if (isUndef(inNum)) {
- inNum = 0;
- }
- this.connect(B.input[inNum]);
- } else {
- this.connect(B.input, outNum, inNum);
- }
- } else {
- try {
- if (B instanceof AudioNode) {
- this._nativeConnect(B, outNum, inNum);
- } else {
- this._nativeConnect(B, outNum);
- }
- } catch (e) {
- throw new Error('error connecting to node: ' + B);
- }
- }
- };
- var Tone = function (inputs, outputs) {
- if (isUndef(inputs) || inputs === 1) {
- this.input = this.context.createGain();
- } else if (inputs > 1) {
- this.input = new Array(inputs);
- }
- if (isUndef(outputs) || outputs === 1) {
- this.output = this.context.createGain();
- } else if (outputs > 1) {
- this.output = new Array(inputs);
- }
- };
- Tone.prototype.set = function (params, value, rampTime) {
- if (this.isObject(params)) {
- rampTime = value;
- } else if (this.isString(params)) {
- var tmpObj = {};
- tmpObj[params] = value;
- params = tmpObj;
- }
- for (var attr in params) {
- value = params[attr];
- var parent = this;
- if (attr.indexOf('.') !== -1) {
- var attrSplit = attr.split('.');
- for (var i = 0; i < attrSplit.length - 1; i++) {
- parent = parent[attrSplit[i]];
- }
- attr = attrSplit[attrSplit.length - 1];
- }
- var param = parent[attr];
- if (isUndef(param)) {
- continue;
- }
- if (Tone.Signal && param instanceof Tone.Signal || Tone.Param && param instanceof Tone.Param) {
- if (param.value !== value) {
- if (isUndef(rampTime)) {
- param.value = value;
- } else {
- param.rampTo(value, rampTime);
- }
- }
- } else if (param instanceof AudioParam) {
- if (param.value !== value) {
- param.value = value;
- }
- } else if (param instanceof Tone) {
- param.set(value);
- } else if (param !== value) {
- parent[attr] = value;
- }
- }
- return this;
- };
- Tone.prototype.get = function (params) {
- if (isUndef(params)) {
- params = this._collectDefaults(this.constructor);
- } else if (this.isString(params)) {
- params = [params];
- }
- var ret = {};
- for (var i = 0; i < params.length; i++) {
- var attr = params[i];
- var parent = this;
- var subRet = ret;
- if (attr.indexOf('.') !== -1) {
- var attrSplit = attr.split('.');
- for (var j = 0; j < attrSplit.length - 1; j++) {
- var subAttr = attrSplit[j];
- subRet[subAttr] = subRet[subAttr] || {};
- subRet = subRet[subAttr];
- parent = parent[subAttr];
- }
- attr = attrSplit[attrSplit.length - 1];
- }
- var param = parent[attr];
- if (this.isObject(params[attr])) {
- subRet[attr] = param.get();
- } else if (Tone.Signal && param instanceof Tone.Signal) {
- subRet[attr] = param.value;
- } else if (Tone.Param && param instanceof Tone.Param) {
- subRet[attr] = param.value;
- } else if (param instanceof AudioParam) {
- subRet[attr] = param.value;
- } else if (param instanceof Tone) {
- subRet[attr] = param.get();
- } else if (!isFunction(param) && !isUndef(param)) {
- subRet[attr] = param;
- }
- }
- return ret;
- };
- Tone.prototype._collectDefaults = function (constr) {
- var ret = [];
- if (!isUndef(constr.defaults)) {
- ret = Object.keys(constr.defaults);
- }
- if (!isUndef(constr._super)) {
- var superDefs = this._collectDefaults(constr._super);
- for (var i = 0; i < superDefs.length; i++) {
- if (ret.indexOf(superDefs[i]) === -1) {
- ret.push(superDefs[i]);
- }
- }
- }
- return ret;
- };
- Tone.prototype.toString = function () {
- for (var className in Tone) {
- var isLetter = className[0].match(/^[A-Z]$/);
- var sameConstructor = Tone[className] === this.constructor;
- if (isFunction(Tone[className]) && isLetter && sameConstructor) {
- return className;
- }
- }
- return 'Tone';
- };
- Tone.context = audioContext;
- Tone.prototype.context = Tone.context;
- Tone.prototype.bufferSize = 2048;
- Tone.prototype.blockTime = 128 / Tone.context.sampleRate;
- Tone.prototype.dispose = function () {
- if (!this.isUndef(this.input)) {
- if (this.input instanceof AudioNode) {
- this.input.disconnect();
- }
- this.input = null;
- }
- if (!this.isUndef(this.output)) {
- if (this.output instanceof AudioNode) {
- this.output.disconnect();
- }
- this.output = null;
- }
- return this;
- };
- var _silentNode = null;
- Tone.prototype.noGC = function () {
- this.output.connect(_silentNode);
- return this;
- };
- AudioNode.prototype.noGC = function () {
- this.connect(_silentNode);
- return this;
- };
- Tone.prototype.connect = function (unit, outputNum, inputNum) {
- if (Array.isArray(this.output)) {
- outputNum = this.defaultArg(outputNum, 0);
- this.output[outputNum].connect(unit, 0, inputNum);
- } else {
- this.output.connect(unit, outputNum, inputNum);
- }
- return this;
- };
- Tone.prototype.disconnect = function (outputNum) {
- if (Array.isArray(this.output)) {
- outputNum = this.defaultArg(outputNum, 0);
- this.output[outputNum].disconnect();
- } else {
- this.output.disconnect();
- }
- return this;
- };
- Tone.prototype.connectSeries = function () {
- if (arguments.length > 1) {
- var currentUnit = arguments[0];
- for (var i = 1; i < arguments.length; i++) {
- var toUnit = arguments[i];
- currentUnit.connect(toUnit);
- currentUnit = toUnit;
- }
- }
- return this;
- };
- Tone.prototype.connectParallel = function () {
- var connectFrom = arguments[0];
- if (arguments.length > 1) {
- for (var i = 1; i < arguments.length; i++) {
- var connectTo = arguments[i];
- connectFrom.connect(connectTo);
- }
- }
- return this;
- };
- Tone.prototype.chain = function () {
- if (arguments.length > 0) {
- var currentUnit = this;
- for (var i = 0; i < arguments.length; i++) {
- var toUnit = arguments[i];
- currentUnit.connect(toUnit);
- currentUnit = toUnit;
- }
- }
- return this;
- };
- Tone.prototype.fan = function () {
- if (arguments.length > 0) {
- for (var i = 0; i < arguments.length; i++) {
- this.connect(arguments[i]);
- }
- }
- return this;
- };
- AudioNode.prototype.chain = Tone.prototype.chain;
- AudioNode.prototype.fan = Tone.prototype.fan;
- Tone.prototype.defaultArg = function (given, fallback) {
- if (this.isObject(given) && this.isObject(fallback)) {
- var ret = {};
- for (var givenProp in given) {
- ret[givenProp] = this.defaultArg(fallback[givenProp], given[givenProp]);
- }
- for (var fallbackProp in fallback) {
- ret[fallbackProp] = this.defaultArg(given[fallbackProp], fallback[fallbackProp]);
- }
- return ret;
- } else {
- return isUndef(given) ? fallback : given;
- }
- };
- Tone.prototype.optionsObject = function (values, keys, defaults) {
- var options = {};
- if (values.length === 1 && this.isObject(values[0])) {
- options = values[0];
- } else {
- for (var i = 0; i < keys.length; i++) {
- options[keys[i]] = values[i];
- }
- }
- if (!this.isUndef(defaults)) {
- return this.defaultArg(options, defaults);
- } else {
- return options;
- }
- };
- Tone.prototype.isUndef = isUndef;
- Tone.prototype.isFunction = isFunction;
- Tone.prototype.isNumber = function (arg) {
- return typeof arg === 'number';
- };
- Tone.prototype.isObject = function (arg) {
- return Object.prototype.toString.call(arg) === '[object Object]' && arg.constructor === Object;
- };
- Tone.prototype.isBoolean = function (arg) {
- return typeof arg === 'boolean';
- };
- Tone.prototype.isArray = function (arg) {
- return Array.isArray(arg);
- };
- Tone.prototype.isString = function (arg) {
- return typeof arg === 'string';
- };
- Tone.noOp = function () {
- };
- Tone.prototype._readOnly = function (property) {
- if (Array.isArray(property)) {
- for (var i = 0; i < property.length; i++) {
- this._readOnly(property[i]);
- }
- } else {
- Object.defineProperty(this, property, {
- writable: false,
- enumerable: true
- });
- }
- };
- Tone.prototype._writable = function (property) {
- if (Array.isArray(property)) {
- for (var i = 0; i < property.length; i++) {
- this._writable(property[i]);
- }
- } else {
- Object.defineProperty(this, property, { writable: true });
- }
- };
- Tone.State = {
- Started: 'started',
- Stopped: 'stopped',
- Paused: 'paused'
- };
- Tone.prototype.equalPowerScale = function (percent) {
- var piFactor = 0.5 * Math.PI;
- return Math.sin(percent * piFactor);
- };
- Tone.prototype.dbToGain = function (db) {
- return Math.pow(2, db / 6);
- };
- Tone.prototype.gainToDb = function (gain) {
- return 20 * (Math.log(gain) / Math.LN10);
- };
- Tone.prototype.now = function () {
- return this.context.currentTime;
- };
- Tone.extend = function (child, parent) {
- if (isUndef(parent)) {
- parent = Tone;
- }
- function TempConstructor() {
- }
- TempConstructor.prototype = parent.prototype;
- child.prototype = new TempConstructor();
- child.prototype.constructor = child;
- child._super = parent;
- };
- var newContextCallbacks = [];
- Tone._initAudioContext = function (callback) {
- callback(Tone.context);
- newContextCallbacks.push(callback);
- };
- Tone.setContext = function (ctx) {
- Tone.prototype.context = ctx;
- Tone.context = ctx;
- for (var i = 0; i < newContextCallbacks.length; i++) {
- newContextCallbacks[i](ctx);
- }
- };
- Tone.startMobile = function () {
- var osc = Tone.context.createOscillator();
- var silent = Tone.context.createGain();
- silent.gain.value = 0;
- osc.connect(silent);
- silent.connect(Tone.context.destination);
- var now = Tone.context.currentTime;
- osc.start(now);
- osc.stop(now + 1);
- };
- Tone._initAudioContext(function (audioContext) {
- Tone.prototype.blockTime = 128 / audioContext.sampleRate;
- _silentNode = audioContext.createGain();
- _silentNode.gain.value = 0;
- _silentNode.connect(audioContext.destination);
- });
- Tone.version = 'r7-dev';
- return Tone;
- }();
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_signal_SignalBase;
- Tone_signal_SignalBase = function (Tone) {
- 'use strict';
- Tone.SignalBase = function () {
- };
- Tone.extend(Tone.SignalBase);
- Tone.SignalBase.prototype.connect = function (node, outputNumber, inputNumber) {
- if (Tone.Signal && Tone.Signal === node.constructor || Tone.Param && Tone.Param === node.constructor || Tone.TimelineSignal && Tone.TimelineSignal === node.constructor) {
- node._param.cancelScheduledValues(0);
- node._param.value = 0;
- node.overridden = true;
- } else if (node instanceof AudioParam) {
- node.cancelScheduledValues(0);
- node.value = 0;
- }
- Tone.prototype.connect.call(this, node, outputNumber, inputNumber);
- return this;
- };
- return Tone.SignalBase;
- }(Tone_core_Tone);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_signal_WaveShaper;
- Tone_signal_WaveShaper = function (Tone) {
- 'use strict';
- Tone.WaveShaper = function (mapping, bufferLen) {
- this._shaper = this.input = this.output = this.context.createWaveShaper();
- this._curve = null;
- if (Array.isArray(mapping)) {
- this.curve = mapping;
- } else if (isFinite(mapping) || this.isUndef(mapping)) {
- this._curve = new Float32Array(this.defaultArg(mapping, 1024));
- } else if (this.isFunction(mapping)) {
- this._curve = new Float32Array(this.defaultArg(bufferLen, 1024));
- this.setMap(mapping);
- }
- };
- Tone.extend(Tone.WaveShaper, Tone.SignalBase);
- Tone.WaveShaper.prototype.setMap = function (mapping) {
- for (var i = 0, len = this._curve.length; i < len; i++) {
- var normalized = i / len * 2 - 1;
- this._curve[i] = mapping(normalized, i);
- }
- this._shaper.curve = this._curve;
- return this;
- };
- Object.defineProperty(Tone.WaveShaper.prototype, 'curve', {
- get: function () {
- return this._shaper.curve;
- },
- set: function (mapping) {
- this._curve = new Float32Array(mapping);
- this._shaper.curve = this._curve;
- }
- });
- Object.defineProperty(Tone.WaveShaper.prototype, 'oversample', {
- get: function () {
- return this._shaper.oversample;
- },
- set: function (oversampling) {
- if ([
- 'none',
- '2x',
- '4x'
- ].indexOf(oversampling) !== -1) {
- this._shaper.oversample = oversampling;
- } else {
- throw new Error('invalid oversampling: ' + oversampling);
- }
- }
- });
- Tone.WaveShaper.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._shaper.disconnect();
- this._shaper = null;
- this._curve = null;
- return this;
- };
- return Tone.WaveShaper;
- }(Tone_core_Tone);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_core_Type;
- Tone_core_Type = function (Tone) {
- 'use strict';
- Tone.Type = {
- Default: 'number',
- Time: 'time',
- Frequency: 'frequency',
- NormalRange: 'normalRange',
- AudioRange: 'audioRange',
- Decibels: 'db',
- Interval: 'interval',
- BPM: 'bpm',
- Positive: 'positive',
- Cents: 'cents',
- Degrees: 'degrees',
- MIDI: 'midi',
- TransportTime: 'transportTime',
- Ticks: 'tick',
- Note: 'note',
- Milliseconds: 'milliseconds',
- Notation: 'notation'
- };
- Tone.prototype.isNowRelative = function () {
- var nowRelative = new RegExp(/^\s*\+(.)+/i);
- return function (note) {
- return nowRelative.test(note);
- };
- }();
- Tone.prototype.isTicks = function () {
- var tickFormat = new RegExp(/^\d+i$/i);
- return function (note) {
- return tickFormat.test(note);
- };
- }();
- Tone.prototype.isNotation = function () {
- var notationFormat = new RegExp(/^[0-9]+[mnt]$/i);
- return function (note) {
- return notationFormat.test(note);
- };
- }();
- Tone.prototype.isTransportTime = function () {
- var transportTimeFormat = new RegExp(/^(\d+(\.\d+)?\:){1,2}(\d+(\.\d+)?)?$/i);
- return function (transportTime) {
- return transportTimeFormat.test(transportTime);
- };
- }();
- Tone.prototype.isNote = function () {
- var noteFormat = new RegExp(/^[a-g]{1}(b|#|x|bb)?-?[0-9]+$/i);
- return function (note) {
- return noteFormat.test(note);
- };
- }();
- Tone.prototype.isFrequency = function () {
- var freqFormat = new RegExp(/^\d*\.?\d+hz$/i);
- return function (freq) {
- return freqFormat.test(freq);
- };
- }();
- function getTransportBpm() {
- if (Tone.Transport && Tone.Transport.bpm) {
- return Tone.Transport.bpm.value;
- } else {
- return 120;
- }
- }
- function getTransportTimeSignature() {
- if (Tone.Transport && Tone.Transport.timeSignature) {
- return Tone.Transport.timeSignature;
- } else {
- return 4;
- }
- }
- Tone.prototype.notationToSeconds = function (notation, bpm, timeSignature) {
- bpm = this.defaultArg(bpm, getTransportBpm());
- timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature());
- var beatTime = 60 / bpm;
- if (notation === '1n') {
- notation = '1m';
- }
- var subdivision = parseInt(notation, 10);
- var beats = 0;
- if (subdivision === 0) {
- beats = 0;
- }
- var lastLetter = notation.slice(-1);
- if (lastLetter === 't') {
- beats = 4 / subdivision * 2 / 3;
- } else if (lastLetter === 'n') {
- beats = 4 / subdivision;
- } else if (lastLetter === 'm') {
- beats = subdivision * timeSignature;
- } else {
- beats = 0;
- }
- return beatTime * beats;
- };
- Tone.prototype.transportTimeToSeconds = function (transportTime, bpm, timeSignature) {
- bpm = this.defaultArg(bpm, getTransportBpm());
- timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature());
- var measures = 0;
- var quarters = 0;
- var sixteenths = 0;
- var split = transportTime.split(':');
- if (split.length === 2) {
- measures = parseFloat(split[0]);
- quarters = parseFloat(split[1]);
- } else if (split.length === 1) {
- quarters = parseFloat(split[0]);
- } else if (split.length === 3) {
- measures = parseFloat(split[0]);
- quarters = parseFloat(split[1]);
- sixteenths = parseFloat(split[2]);
- }
- var beats = measures * timeSignature + quarters + sixteenths / 4;
- return beats * (60 / bpm);
- };
- Tone.prototype.ticksToSeconds = function (ticks, bpm) {
- if (this.isUndef(Tone.Transport)) {
- return 0;
- }
- ticks = parseFloat(ticks);
- bpm = this.defaultArg(bpm, getTransportBpm());
- var tickTime = 60 / bpm / Tone.Transport.PPQ;
- return tickTime * ticks;
- };
- Tone.prototype.frequencyToSeconds = function (freq) {
- return 1 / parseFloat(freq);
- };
- Tone.prototype.samplesToSeconds = function (samples) {
- return samples / this.context.sampleRate;
- };
- Tone.prototype.secondsToSamples = function (seconds) {
- return seconds * this.context.sampleRate;
- };
- Tone.prototype.secondsToTransportTime = function (seconds, bpm, timeSignature) {
- bpm = this.defaultArg(bpm, getTransportBpm());
- timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature());
- var quarterTime = 60 / bpm;
- var quarters = seconds / quarterTime;
- var measures = Math.floor(quarters / timeSignature);
- var sixteenths = quarters % 1 * 4;
- quarters = Math.floor(quarters) % timeSignature;
- var progress = [
- measures,
- quarters,
- sixteenths
- ];
- return progress.join(':');
- };
- Tone.prototype.secondsToFrequency = function (seconds) {
- return 1 / seconds;
- };
- Tone.prototype.toTransportTime = function (time, bpm, timeSignature) {
- var seconds = this.toSeconds(time);
- return this.secondsToTransportTime(seconds, bpm, timeSignature);
- };
- Tone.prototype.toFrequency = function (freq, now) {
- if (this.isFrequency(freq)) {
- return parseFloat(freq);
- } else if (this.isNotation(freq) || this.isTransportTime(freq)) {
- return this.secondsToFrequency(this.toSeconds(freq, now));
- } else if (this.isNote(freq)) {
- return this.noteToFrequency(freq);
- } else {
- return freq;
- }
- };
- Tone.prototype.toTicks = function (time) {
- if (this.isUndef(Tone.Transport)) {
- return 0;
- }
- var bpm = Tone.Transport.bpm.value;
- var plusNow = 0;
- if (this.isNowRelative(time)) {
- time = time.replace('+', '');
- plusNow = Tone.Transport.ticks;
- } else if (this.isUndef(time)) {
- return Tone.Transport.ticks;
- }
- var seconds = this.toSeconds(time);
- var quarter = 60 / bpm;
- var quarters = seconds / quarter;
- var tickNum = quarters * Tone.Transport.PPQ;
- return Math.round(tickNum + plusNow);
- };
- Tone.prototype.toSamples = function (time) {
- var seconds = this.toSeconds(time);
- return Math.round(seconds * this.context.sampleRate);
- };
- Tone.prototype.toSeconds = function (time, now) {
- now = this.defaultArg(now, this.now());
- if (this.isNumber(time)) {
- return time;
- } else if (this.isString(time)) {
- var plusTime = 0;
- if (this.isNowRelative(time)) {
- time = time.replace('+', '');
- plusTime = now;
- }
- var betweenParens = time.match(/\(([^)(]+)\)/g);
- if (betweenParens) {
- for (var j = 0; j < betweenParens.length; j++) {
- var symbol = betweenParens[j].replace(/[\(\)]/g, '');
- var symbolVal = this.toSeconds(symbol);
- time = time.replace(betweenParens[j], symbolVal);
- }
- }
- if (time.indexOf('@') !== -1) {
- var quantizationSplit = time.split('@');
- if (!this.isUndef(Tone.Transport)) {
- var toQuantize = quantizationSplit[0].trim();
- if (toQuantize === '') {
- toQuantize = undefined;
- }
- if (plusTime > 0) {
- toQuantize = '+' + toQuantize;
- plusTime = 0;
- }
- var subdivision = quantizationSplit[1].trim();
- time = Tone.Transport.quantize(toQuantize, subdivision);
- } else {
- throw new Error('quantization requires Tone.Transport');
- }
- } else {
- var components = time.split(/[\(\)\-\+\/\*]/);
- if (components.length > 1) {
- var originalTime = time;
- for (var i = 0; i < components.length; i++) {
- var symb = components[i].trim();
- if (symb !== '') {
- var val = this.toSeconds(symb);
- time = time.replace(symb, val);
- }
- }
- try {
- time = eval(time);
- } catch (e) {
- throw new EvalError('cannot evaluate Time: ' + originalTime);
- }
- } else if (this.isNotation(time)) {
- time = this.notationToSeconds(time);
- } else if (this.isTransportTime(time)) {
- time = this.transportTimeToSeconds(time);
- } else if (this.isFrequency(time)) {
- time = this.frequencyToSeconds(time);
- } else if (this.isTicks(time)) {
- time = this.ticksToSeconds(time);
- } else {
- time = parseFloat(time);
- }
- }
- return time + plusTime;
- } else {
- return now;
- }
- };
- Tone.prototype.toNotation = function (time, bpm, timeSignature) {
- var testNotations = [
- '1m',
- '2n',
- '4n',
- '8n',
- '16n',
- '32n',
- '64n',
- '128n'
- ];
- var retNotation = toNotationHelper.call(this, time, bpm, timeSignature, testNotations);
- var testTripletNotations = [
- '1m',
- '2n',
- '2t',
- '4n',
- '4t',
- '8n',
- '8t',
- '16n',
- '16t',
- '32n',
- '32t',
- '64n',
- '64t',
- '128n'
- ];
- var retTripletNotation = toNotationHelper.call(this, time, bpm, timeSignature, testTripletNotations);
- if (retTripletNotation.split('+').length < retNotation.split('+').length) {
- return retTripletNotation;
- } else {
- return retNotation;
- }
- };
- function toNotationHelper(time, bpm, timeSignature, testNotations) {
- var seconds = this.toSeconds(time);
- var threshold = this.notationToSeconds(testNotations[testNotations.length - 1], bpm, timeSignature);
- var retNotation = '';
- for (var i = 0; i < testNotations.length; i++) {
- var notationTime = this.notationToSeconds(testNotations[i], bpm, timeSignature);
- var multiple = seconds / notationTime;
- var floatingPointError = 0.000001;
- if (1 - multiple % 1 < floatingPointError) {
- multiple += floatingPointError;
- }
- multiple = Math.floor(multiple);
- if (multiple > 0) {
- if (multiple === 1) {
- retNotation += testNotations[i];
- } else {
- retNotation += multiple.toString() + '*' + testNotations[i];
- }
- seconds -= multiple * notationTime;
- if (seconds < threshold) {
- break;
- } else {
- retNotation += ' + ';
- }
- }
- }
- if (retNotation === '') {
- retNotation = '0';
- }
- return retNotation;
- }
- Tone.prototype.fromUnits = function (val, units) {
- if (this.convert || this.isUndef(this.convert)) {
- switch (units) {
- case Tone.Type.Time:
- return this.toSeconds(val);
- case Tone.Type.Frequency:
- return this.toFrequency(val);
- case Tone.Type.Decibels:
- return this.dbToGain(val);
- case Tone.Type.NormalRange:
- return Math.min(Math.max(val, 0), 1);
- case Tone.Type.AudioRange:
- return Math.min(Math.max(val, -1), 1);
- case Tone.Type.Positive:
- return Math.max(val, 0);
- default:
- return val;
- }
- } else {
- return val;
- }
- };
- Tone.prototype.toUnits = function (val, units) {
- if (this.convert || this.isUndef(this.convert)) {
- switch (units) {
- case Tone.Type.Decibels:
- return this.gainToDb(val);
- default:
- return val;
- }
- } else {
- return val;
- }
- };
- var noteToScaleIndex = {
- 'cbb': -2,
- 'cb': -1,
- 'c': 0,
- 'c#': 1,
- 'cx': 2,
- 'dbb': 0,
- 'db': 1,
- 'd': 2,
- 'd#': 3,
- 'dx': 4,
- 'ebb': 2,
- 'eb': 3,
- 'e': 4,
- 'e#': 5,
- 'ex': 6,
- 'fbb': 3,
- 'fb': 4,
- 'f': 5,
- 'f#': 6,
- 'fx': 7,
- 'gbb': 5,
- 'gb': 6,
- 'g': 7,
- 'g#': 8,
- 'gx': 9,
- 'abb': 7,
- 'ab': 8,
- 'a': 9,
- 'a#': 10,
- 'ax': 11,
- 'bbb': 9,
- 'bb': 10,
- 'b': 11,
- 'b#': 12,
- 'bx': 13
- };
- var scaleIndexToNote = [
- 'C',
- 'C#',
- 'D',
- 'D#',
- 'E',
- 'F',
- 'F#',
- 'G',
- 'G#',
- 'A',
- 'A#',
- 'B'
- ];
- Tone.A4 = 440;
- Tone.prototype.noteToFrequency = function (note) {
- var parts = note.split(/(-?\d+)/);
- if (parts.length === 3) {
- var index = noteToScaleIndex[parts[0].toLowerCase()];
- var octave = parts[1];
- var noteNumber = index + (parseInt(octave, 10) + 1) * 12;
- return this.midiToFrequency(noteNumber);
- } else {
- return 0;
- }
- };
- Tone.prototype.frequencyToNote = function (freq) {
- var log = Math.log(freq / Tone.A4) / Math.LN2;
- var noteNumber = Math.round(12 * log) + 57;
- var octave = Math.floor(noteNumber / 12);
- if (octave < 0) {
- noteNumber += -12 * octave;
- }
- var noteName = scaleIndexToNote[noteNumber % 12];
- return noteName + octave.toString();
- };
- Tone.prototype.intervalToFrequencyRatio = function (interval) {
- return Math.pow(2, interval / 12);
- };
- Tone.prototype.midiToNote = function (midiNumber) {
- var octave = Math.floor(midiNumber / 12) - 1;
- var note = midiNumber % 12;
- return scaleIndexToNote[note] + octave;
- };
- Tone.prototype.noteToMidi = function (note) {
- var parts = note.split(/(\d+)/);
- if (parts.length === 3) {
- var index = noteToScaleIndex[parts[0].toLowerCase()];
- var octave = parts[1];
- return index + (parseInt(octave, 10) + 1) * 12;
- } else {
- return 0;
- }
- };
- Tone.prototype.midiToFrequency = function (midi) {
- return Tone.A4 * Math.pow(2, (midi - 69) / 12);
- };
- return Tone;
- }(Tone_core_Tone);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_core_Param;
- Tone_core_Param = function (Tone) {
- 'use strict';
- Tone.Param = function () {
- var options = this.optionsObject(arguments, [
- 'param',
- 'units',
- 'convert'
- ], Tone.Param.defaults);
- this._param = this.input = options.param;
- this.units = options.units;
- this.convert = options.convert;
- this.overridden = false;
- if (!this.isUndef(options.value)) {
- this.value = options.value;
- }
- };
- Tone.extend(Tone.Param);
- Tone.Param.defaults = {
- 'units': Tone.Type.Default,
- 'convert': true,
- 'param': undefined
- };
- Object.defineProperty(Tone.Param.prototype, 'value', {
- get: function () {
- return this._toUnits(this._param.value);
- },
- set: function (value) {
- var convertedVal = this._fromUnits(value);
- this._param.value = convertedVal;
- }
- });
- Tone.Param.prototype._fromUnits = function (val) {
- if (this.convert || this.isUndef(this.convert)) {
- switch (this.units) {
- case Tone.Type.Time:
- return this.toSeconds(val);
- case Tone.Type.Frequency:
- return this.toFrequency(val);
- case Tone.Type.Decibels:
- return this.dbToGain(val);
- case Tone.Type.NormalRange:
- return Math.min(Math.max(val, 0), 1);
- case Tone.Type.AudioRange:
- return Math.min(Math.max(val, -1), 1);
- case Tone.Type.Positive:
- return Math.max(val, 0);
- default:
- return val;
- }
- } else {
- return val;
- }
- };
- Tone.Param.prototype._toUnits = function (val) {
- if (this.convert || this.isUndef(this.convert)) {
- switch (this.units) {
- case Tone.Type.Decibels:
- return this.gainToDb(val);
- default:
- return val;
- }
- } else {
- return val;
- }
- };
- Tone.Param.prototype._minOutput = 0.00001;
- Tone.Param.prototype.setValueAtTime = function (value, time) {
- value = this._fromUnits(value);
- this._param.setValueAtTime(value, this.toSeconds(time));
- return this;
- };
- Tone.Param.prototype.setRampPoint = function (now) {
- now = this.defaultArg(now, this.now());
- var currentVal = this._param.value;
- this._param.setValueAtTime(currentVal, now);
- return this;
- };
- Tone.Param.prototype.linearRampToValueAtTime = function (value, endTime) {
- value = this._fromUnits(value);
- this._param.linearRampToValueAtTime(value, this.toSeconds(endTime));
- return this;
- };
- Tone.Param.prototype.exponentialRampToValueAtTime = function (value, endTime) {
- value = this._fromUnits(value);
- value = Math.max(this._minOutput, value);
- this._param.exponentialRampToValueAtTime(value, this.toSeconds(endTime));
- return this;
- };
- Tone.Param.prototype.exponentialRampToValue = function (value, rampTime) {
- var now = this.now();
- var currentVal = this.value;
- this.setValueAtTime(Math.max(currentVal, this._minOutput), now);
- this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime));
- return this;
- };
- Tone.Param.prototype.linearRampToValue = function (value, rampTime) {
- var now = this.now();
- this.setRampPoint(now);
- this.linearRampToValueAtTime(value, now + this.toSeconds(rampTime));
- return this;
- };
- Tone.Param.prototype.setTargetAtTime = function (value, startTime, timeConstant) {
- value = this._fromUnits(value);
- value = Math.max(this._minOutput, value);
- timeConstant = Math.max(this._minOutput, timeConstant);
- this._param.setTargetAtTime(value, this.toSeconds(startTime), timeConstant);
- return this;
- };
- Tone.Param.prototype.setValueCurveAtTime = function (values, startTime, duration) {
- for (var i = 0; i < values.length; i++) {
- values[i] = this._fromUnits(values[i]);
- }
- this._param.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration));
- return this;
- };
- Tone.Param.prototype.cancelScheduledValues = function (startTime) {
- this._param.cancelScheduledValues(this.toSeconds(startTime));
- return this;
- };
- Tone.Param.prototype.rampTo = function (value, rampTime) {
- rampTime = this.defaultArg(rampTime, 0);
- if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM) {
- this.exponentialRampToValue(value, rampTime);
- } else {
- this.linearRampToValue(value, rampTime);
- }
- return this;
- };
- Tone.Param.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._param = null;
- return this;
- };
- return Tone.Param;
- }(Tone_core_Tone);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_core_Gain;
- Tone_core_Gain = function (Tone) {
- 'use strict';
- Tone.Gain = function () {
- var options = this.optionsObject(arguments, [
- 'gain',
- 'units'
- ], Tone.Gain.defaults);
- this.input = this.output = this._gainNode = this.context.createGain();
- this.gain = new Tone.Param({
- 'param': this._gainNode.gain,
- 'units': options.units,
- 'value': options.gain,
- 'convert': options.convert
- });
- this._readOnly('gain');
- };
- Tone.extend(Tone.Gain);
- Tone.Gain.defaults = {
- 'gain': 1,
- 'convert': true
- };
- Tone.Gain.prototype.dispose = function () {
- Tone.Param.prototype.dispose.call(this);
- this._gainNode.disconnect();
- this._gainNode = null;
- this._writable('gain');
- this.gain.dispose();
- this.gain = null;
- };
- return Tone.Gain;
- }(Tone_core_Tone, Tone_core_Param);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_signal_Signal;
- Tone_signal_Signal = function (Tone) {
- 'use strict';
- Tone.Signal = function () {
- var options = this.optionsObject(arguments, [
- 'value',
- 'units'
- ], Tone.Signal.defaults);
- this.output = this._gain = this.context.createGain();
- options.param = this._gain.gain;
- Tone.Param.call(this, options);
- this.input = this._param = this._gain.gain;
- Tone.Signal._constant.chain(this._gain);
- };
- Tone.extend(Tone.Signal, Tone.Param);
- Tone.Signal.defaults = {
- 'value': 0,
- 'units': Tone.Type.Default,
- 'convert': true
- };
- Tone.Signal.prototype.connect = Tone.SignalBase.prototype.connect;
- Tone.Signal.prototype.dispose = function () {
- Tone.Param.prototype.dispose.call(this);
- this._param = null;
- this._gain.disconnect();
- this._gain = null;
- return this;
- };
- Tone.Signal._constant = null;
- Tone._initAudioContext(function (audioContext) {
- var buffer = audioContext.createBuffer(1, 128, audioContext.sampleRate);
- var arr = buffer.getChannelData(0);
- for (var i = 0; i < arr.length; i++) {
- arr[i] = 1;
- }
- Tone.Signal._constant = audioContext.createBufferSource();
- Tone.Signal._constant.channelCount = 1;
- Tone.Signal._constant.channelCountMode = 'explicit';
- Tone.Signal._constant.buffer = buffer;
- Tone.Signal._constant.loop = true;
- Tone.Signal._constant.start(0);
- Tone.Signal._constant.noGC();
- });
- return Tone.Signal;
- }(Tone_core_Tone, Tone_signal_WaveShaper, Tone_core_Type, Tone_core_Param);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_signal_Add;
- Tone_signal_Add = function (Tone) {
- 'use strict';
- Tone.Add = function (value) {
- Tone.call(this, 2, 0);
- this._sum = this.input[0] = this.input[1] = this.output = this.context.createGain();
- this._param = this.input[1] = new Tone.Signal(value);
- this._param.connect(this._sum);
- };
- Tone.extend(Tone.Add, Tone.Signal);
- Tone.Add.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._sum.disconnect();
- this._sum = null;
- this._param.dispose();
- this._param = null;
- return this;
- };
- return Tone.Add;
- }(Tone_core_Tone);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_signal_Multiply;
- Tone_signal_Multiply = function (Tone) {
- 'use strict';
- Tone.Multiply = function (value) {
- Tone.call(this, 2, 0);
- this._mult = this.input[0] = this.output = this.context.createGain();
- this._param = this.input[1] = this.output.gain;
- this._param.value = this.defaultArg(value, 0);
- };
- Tone.extend(Tone.Multiply, Tone.Signal);
- Tone.Multiply.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._mult.disconnect();
- this._mult = null;
- this._param = null;
- return this;
- };
- return Tone.Multiply;
- }(Tone_core_Tone);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_signal_Scale;
- Tone_signal_Scale = function (Tone) {
- 'use strict';
- Tone.Scale = function (outputMin, outputMax) {
- this._outputMin = this.defaultArg(outputMin, 0);
- this._outputMax = this.defaultArg(outputMax, 1);
- this._scale = this.input = new Tone.Multiply(1);
- this._add = this.output = new Tone.Add(0);
- this._scale.connect(this._add);
- this._setRange();
- };
- Tone.extend(Tone.Scale, Tone.SignalBase);
- Object.defineProperty(Tone.Scale.prototype, 'min', {
- get: function () {
- return this._outputMin;
- },
- set: function (min) {
- this._outputMin = min;
- this._setRange();
- }
- });
- Object.defineProperty(Tone.Scale.prototype, 'max', {
- get: function () {
- return this._outputMax;
- },
- set: function (max) {
- this._outputMax = max;
- this._setRange();
- }
- });
- Tone.Scale.prototype._setRange = function () {
- this._add.value = this._outputMin;
- this._scale.value = this._outputMax - this._outputMin;
- };
- Tone.Scale.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._add.dispose();
- this._add = null;
- this._scale.dispose();
- this._scale = null;
- return this;
- };
- return Tone.Scale;
- }(Tone_core_Tone, Tone_signal_Add, Tone_signal_Multiply);
- var signal;
- signal = function () {
- 'use strict';
- // Signal is built with the Tone.js signal by Yotam Mann
- // https://github.com/TONEnoTONE/Tone.js/
- var Signal = Tone_signal_Signal;
- var Add = Tone_signal_Add;
- var Mult = Tone_signal_Multiply;
- var Scale = Tone_signal_Scale;
- var Tone = Tone_core_Tone;
- var p5sound = master;
- Tone.setContext(p5sound.audiocontext);
- /**
- * <p>p5.Signal is a constant audio-rate signal used by p5.Oscillator
- * and p5.Envelope for modulation math.</p>
- *
- * <p>This is necessary because Web Audio is processed on a seprate clock.
- * For example, the p5 draw loop runs about 60 times per second. But
- * the audio clock must process samples 44100 times per second. If we
- * want to add a value to each of those samples, we can't do it in the
- * draw loop, but we can do it by adding a constant-rate audio signal.</p.
- *
- * <p>This class mostly functions behind the scenes in p5.sound, and returns
- * a Tone.Signal from the Tone.js library by Yotam Mann.
- * If you want to work directly with audio signals for modular
- * synthesis, check out
- * <a href='http://bit.ly/1oIoEng' target=_'blank'>tone.js.</a></p>
- *
- * @class p5.Signal
- * @constructor
- * @return {Tone.Signal} A Signal object from the Tone.js library
- * @example
- * <div><code>
- * function setup() {
- * carrier = new p5.Oscillator('sine');
- * carrier.amp(1); // set amplitude
- * carrier.freq(220); // set frequency
- * carrier.start(); // start oscillating
- *
- * modulator = new p5.Oscillator('sawtooth');
- * modulator.disconnect();
- * modulator.amp(1);
- * modulator.freq(4);
- * modulator.start();
- *
- * // Modulator's default amplitude range is -1 to 1.
- * // Multiply it by -200, so the range is -200 to 200
- * // then add 220 so the range is 20 to 420
- * carrier.freq( modulator.mult(-200).add(220) );
- * }
- * </code></div>
- */
- p5.Signal = function (value) {
- var s = new Signal(value);
- // p5sound.soundArray.push(s);
- return s;
- };
- /**
- * Fade to value, for smooth transitions
- *
- * @method fade
- * @param {Number} value Value to set this signal
- * @param {Number} [secondsFromNow] Length of fade, in seconds from now
- */
- Signal.prototype.fade = Signal.prototype.linearRampToValueAtTime;
- Mult.prototype.fade = Signal.prototype.fade;
- Add.prototype.fade = Signal.prototype.fade;
- Scale.prototype.fade = Signal.prototype.fade;
- /**
- * Connect a p5.sound object or Web Audio node to this
- * p5.Signal so that its amplitude values can be scaled.
- *
- * @param {Object} input
- */
- Signal.prototype.setInput = function (_input) {
- _input.connect(this);
- };
- Mult.prototype.setInput = Signal.prototype.setInput;
- Add.prototype.setInput = Signal.prototype.setInput;
- Scale.prototype.setInput = Signal.prototype.setInput;
- // signals can add / mult / scale themselves
- /**
- * Add a constant value to this audio signal,
- * and return the resulting audio signal. Does
- * not change the value of the original signal,
- * instead it returns a new p5.SignalAdd.
- *
- * @method add
- * @param {Number} number
- * @return {p5.SignalAdd} object
- */
- Signal.prototype.add = function (num) {
- var add = new Add(num);
- // add.setInput(this);
- this.connect(add);
- return add;
- };
- Mult.prototype.add = Signal.prototype.add;
- Add.prototype.add = Signal.prototype.add;
- Scale.prototype.add = Signal.prototype.add;
- /**
- * Multiply this signal by a constant value,
- * and return the resulting audio signal. Does
- * not change the value of the original signal,
- * instead it returns a new p5.SignalMult.
- *
- * @method mult
- * @param {Number} number to multiply
- * @return {Tone.Multiply} object
- */
- Signal.prototype.mult = function (num) {
- var mult = new Mult(num);
- // mult.setInput(this);
- this.connect(mult);
- return mult;
- };
- Mult.prototype.mult = Signal.prototype.mult;
- Add.prototype.mult = Signal.prototype.mult;
- Scale.prototype.mult = Signal.prototype.mult;
- /**
- * Scale this signal value to a given range,
- * and return the result as an audio signal. Does
- * not change the value of the original signal,
- * instead it returns a new p5.SignalScale.
- *
- * @method scale
- * @param {Number} number to multiply
- * @param {Number} inMin input range minumum
- * @param {Number} inMax input range maximum
- * @param {Number} outMin input range minumum
- * @param {Number} outMax input range maximum
- * @return {p5.SignalScale} object
- */
- Signal.prototype.scale = function (inMin, inMax, outMin, outMax) {
- var mapOutMin, mapOutMax;
- if (arguments.length === 4) {
- mapOutMin = p5.prototype.map(outMin, inMin, inMax, 0, 1) - 0.5;
- mapOutMax = p5.prototype.map(outMax, inMin, inMax, 0, 1) - 0.5;
- } else {
- mapOutMin = arguments[0];
- mapOutMax = arguments[1];
- }
- var scale = new Scale(mapOutMin, mapOutMax);
- this.connect(scale);
- return scale;
- };
- Mult.prototype.scale = Signal.prototype.scale;
- Add.prototype.scale = Signal.prototype.scale;
- Scale.prototype.scale = Signal.prototype.scale;
- }(Tone_signal_Signal, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_core_Tone, master);
- var oscillator;
- oscillator = function () {
- 'use strict';
- var p5sound = master;
- var Signal = Tone_signal_Signal;
- var Add = Tone_signal_Add;
- var Mult = Tone_signal_Multiply;
- var Scale = Tone_signal_Scale;
- /**
- * <p>Creates a signal that oscillates between -1.0 and 1.0.
- * By default, the oscillation takes the form of a sinusoidal
- * shape ('sine'). Additional types include 'triangle',
- * 'sawtooth' and 'square'. The frequency defaults to
- * 440 oscillations per second (440Hz, equal to the pitch of an
- * 'A' note).</p>
- *
- * <p>Set the type of oscillation with setType(), or by creating a
- * specific oscillator.</p> For example:
- * <code>new p5.SinOsc(freq)</code>
- * <code>new p5.TriOsc(freq)</code>
- * <code>new p5.SqrOsc(freq)</code>
- * <code>new p5.SawOsc(freq)</code>.
- * </p>
- *
- * @class p5.Oscillator
- * @constructor
- * @param {Number} [freq] frequency defaults to 440Hz
- * @param {String} [type] type of oscillator. Options:
- * 'sine' (default), 'triangle',
- * 'sawtooth', 'square'
- * @return {Object} Oscillator object
- * @example
- * <div><code>
- * var osc;
- * var playing = false;
- *
- * function setup() {
- * backgroundColor = color(255,0,255);
- * textAlign(CENTER);
- *
- * osc = new p5.Oscillator();
- * osc.setType('sine');
- * osc.freq(240);
- * osc.amp(0);
- * osc.start();
- * }
- *
- * function draw() {
- * background(backgroundColor)
- * text('click to play', width/2, height/2);
- * }
- *
- * function mouseClicked() {
- * if (mouseX > 0 && mouseX < width && mouseY < height && mouseY > 0) {
- * if (!playing) {
- * // ramp amplitude to 0.5 over 0.1 seconds
- * osc.amp(0.5, 0.05);
- * playing = true;
- * backgroundColor = color(0,255,255);
- * } else {
- * // ramp amplitude to 0 over 0.5 seconds
- * osc.amp(0, 0.5);
- * playing = false;
- * backgroundColor = color(255,0,255);
- * }
- * }
- * }
- * </code> </div>
- */
- p5.Oscillator = function (freq, type) {
- if (typeof freq === 'string') {
- var f = type;
- type = freq;
- freq = f;
- }
- if (typeof type === 'number') {
- var f = type;
- type = freq;
- freq = f;
- }
- this.started = false;
- // components
- this.phaseAmount = undefined;
- this.oscillator = p5sound.audiocontext.createOscillator();
- this.f = freq || 440;
- // frequency
- this.oscillator.type = type || 'sine';
- this.oscillator.frequency.setValueAtTime(this.f, p5sound.audiocontext.currentTime);
- var o = this.oscillator;
- // connections
- this.output = p5sound.audiocontext.createGain();
- this._freqMods = [];
- // modulators connected to this oscillator's frequency
- // set default output gain to 0.5
- this.output.gain.value = 0.5;
- this.output.gain.setValueAtTime(0.5, p5sound.audiocontext.currentTime);
- this.oscillator.connect(this.output);
- // stereo panning
- this.panPosition = 0;
- this.connection = p5sound.input;
- // connect to p5sound by default
- this.panner = new p5.Panner(this.output, this.connection, 1);
- //array of math operation signal chaining
- this.mathOps = [this.output];
- // add to the soundArray so we can dispose of the osc later
- p5sound.soundArray.push(this);
- };
- /**
- * Start an oscillator. Accepts an optional parameter to
- * determine how long (in seconds from now) until the
- * oscillator starts.
- *
- * @method start
- * @param {Number} [time] startTime in seconds from now.
- * @param {Number} [frequency] frequency in Hz.
- */
- p5.Oscillator.prototype.start = function (time, f) {
- if (this.started) {
- var now = p5sound.audiocontext.currentTime;
- this.stop(now);
- }
- if (!this.started) {
- var freq = f || this.f;
- var type = this.oscillator.type;
- // set old osc free to be garbage collected (memory)
- if (this.oscillator) {
- this.oscillator.disconnect();
- this.oscillator = undefined;
- }
- // var detune = this.oscillator.frequency.value;
- this.oscillator = p5sound.audiocontext.createOscillator();
- this.oscillator.frequency.exponentialRampToValueAtTime(Math.abs(freq), p5sound.audiocontext.currentTime);
- this.oscillator.type = type;
- // this.oscillator.detune.value = detune;
- this.oscillator.connect(this.output);
- time = time || 0;
- this.oscillator.start(time + p5sound.audiocontext.currentTime);
- this.freqNode = this.oscillator.frequency;
- // if other oscillators are already connected to this osc's freq
- for (var i in this._freqMods) {
- if (typeof this._freqMods[i].connect !== 'undefined') {
- this._freqMods[i].connect(this.oscillator.frequency);
- }
- }
- this.started = true;
- }
- };
- /**
- * Stop an oscillator. Accepts an optional parameter
- * to determine how long (in seconds from now) until the
- * oscillator stops.
- *
- * @method stop
- * @param {Number} secondsFromNow Time, in seconds from now.
- */
- p5.Oscillator.prototype.stop = function (time) {
- if (this.started) {
- var t = time || 0;
- var now = p5sound.audiocontext.currentTime;
- this.oscillator.stop(t + now);
- this.started = false;
- }
- };
- /**
- * Set the amplitude between 0 and 1.0. Or, pass in an object
- * such as an oscillator to modulate amplitude with an audio signal.
- *
- * @method amp
- * @param {Number|Object} vol between 0 and 1.0
- * or a modulating signal/oscillator
- * @param {Number} [rampTime] create a fade that lasts rampTime
- * @param {Number} [timeFromNow] schedule this event to happen
- * seconds from now
- * @return {AudioParam} gain If no value is provided,
- * returns the Web Audio API
- * AudioParam that controls
- * this oscillator's
- * gain/amplitude/volume)
- */
- p5.Oscillator.prototype.amp = function (vol, rampTime, tFromNow) {
- var self = this;
- if (typeof vol === 'number') {
- var rampTime = rampTime || 0;
- var tFromNow = tFromNow || 0;
- var now = p5sound.audiocontext.currentTime;
- var currentVol = this.output.gain.value;
- this.output.gain.cancelScheduledValues(now);
- this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow);
- this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime);
- } else if (vol) {
- vol.connect(self.output.gain);
- } else {
- // return the Gain Node
- return this.output.gain;
- }
- };
- // these are now the same thing
- p5.Oscillator.prototype.fade = p5.Oscillator.prototype.amp;
- p5.Oscillator.prototype.getAmp = function () {
- return this.output.gain.value;
- };
- /**
- * Set frequency of an oscillator to a value. Or, pass in an object
- * such as an oscillator to modulate the frequency with an audio signal.
- *
- * @method freq
- * @param {Number|Object} Frequency Frequency in Hz
- * or modulating signal/oscillator
- * @param {Number} [rampTime] Ramp time (in seconds)
- * @param {Number} [timeFromNow] Schedule this event to happen
- * at x seconds from now
- * @return {AudioParam} Frequency If no value is provided,
- * returns the Web Audio API
- * AudioParam that controls
- * this oscillator's frequency
- * @example
- * <div><code>
- * var osc = new p5.Oscillator(300);
- * osc.start();
- * osc.freq(40, 10);
- * </code></div>
- */
- p5.Oscillator.prototype.freq = function (val, rampTime, tFromNow) {
- if (typeof val === 'number' && !isNaN(val)) {
- this.f = val;
- var now = p5sound.audiocontext.currentTime;
- var rampTime = rampTime || 0;
- var tFromNow = tFromNow || 0;
- // var currentFreq = this.oscillator.frequency.value;
- // this.oscillator.frequency.cancelScheduledValues(now);
- if (rampTime == 0) {
- this.oscillator.frequency.cancelScheduledValues(now);
- this.oscillator.frequency.setValueAtTime(val, tFromNow + now);
- } else {
- if (val > 0) {
- this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now);
- } else {
- this.oscillator.frequency.linearRampToValueAtTime(val, tFromNow + rampTime + now);
- }
- }
- // reset phase if oscillator has a phase
- if (this.phaseAmount) {
- this.phase(this.phaseAmount);
- }
- } else if (val) {
- if (val.output) {
- val = val.output;
- }
- val.connect(this.oscillator.frequency);
- // keep track of what is modulating this param
- // so it can be re-connected if
- this._freqMods.push(val);
- } else {
- // return the Frequency Node
- return this.oscillator.frequency;
- }
- };
- p5.Oscillator.prototype.getFreq = function () {
- return this.oscillator.frequency.value;
- };
- /**
- * Set type to 'sine', 'triangle', 'sawtooth' or 'square'.
- *
- * @method setType
- * @param {String} type 'sine', 'triangle', 'sawtooth' or 'square'.
- */
- p5.Oscillator.prototype.setType = function (type) {
- this.oscillator.type = type;
- };
- p5.Oscillator.prototype.getType = function () {
- return this.oscillator.type;
- };
- /**
- * Connect to a p5.sound / Web Audio object.
- *
- * @method connect
- * @param {Object} unit A p5.sound or Web Audio object
- */
- p5.Oscillator.prototype.connect = function (unit) {
- if (!unit) {
- this.panner.connect(p5sound.input);
- } else if (unit.hasOwnProperty('input')) {
- this.panner.connect(unit.input);
- this.connection = unit.input;
- } else {
- this.panner.connect(unit);
- this.connection = unit;
- }
- };
- /**
- * Disconnect all outputs
- *
- * @method disconnect
- */
- p5.Oscillator.prototype.disconnect = function (unit) {
- this.output.disconnect();
- this.panner.disconnect();
- this.output.connect(this.panner);
- this.oscMods = [];
- };
- /**
- * Pan between Left (-1) and Right (1)
- *
- * @method pan
- * @param {Number} panning Number between -1 and 1
- * @param {Number} timeFromNow schedule this event to happen
- * seconds from now
- */
- p5.Oscillator.prototype.pan = function (pval, tFromNow) {
- this.panPosition = pval;
- this.panner.pan(pval, tFromNow);
- };
- p5.Oscillator.prototype.getPan = function () {
- return this.panPosition;
- };
- // get rid of the oscillator
- p5.Oscillator.prototype.dispose = function () {
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- if (this.oscillator) {
- var now = p5sound.audiocontext.currentTime;
- this.stop(now);
- this.disconnect();
- this.panner = null;
- this.oscillator = null;
- }
- // if it is a Pulse
- if (this.osc2) {
- this.osc2.dispose();
- }
- };
- /**
- * Set the phase of an oscillator between 0.0 and 1.0.
- * In this implementation, phase is a delay time
- * based on the oscillator's current frequency.
- *
- * @method phase
- * @param {Number} phase float between 0.0 and 1.0
- */
- p5.Oscillator.prototype.phase = function (p) {
- var delayAmt = p5.prototype.map(p, 0, 1, 0, 1 / this.f);
- var now = p5sound.audiocontext.currentTime;
- this.phaseAmount = p;
- if (!this.dNode) {
- // create a delay node
- this.dNode = p5sound.audiocontext.createDelay();
- // put the delay node in between output and panner
- this.oscillator.disconnect();
- this.oscillator.connect(this.dNode);
- this.dNode.connect(this.output);
- }
- // set delay time to match phase:
- this.dNode.delayTime.setValueAtTime(delayAmt, now);
- };
- // ========================== //
- // SIGNAL MATH FOR MODULATION //
- // ========================== //
- // return sigChain(this, scale, thisChain, nextChain, Scale);
- var sigChain = function (o, mathObj, thisChain, nextChain, type) {
- var chainSource = o.oscillator;
- // if this type of math already exists in the chain, replace it
- for (var i in o.mathOps) {
- if (o.mathOps[i] instanceof type) {
- chainSource.disconnect();
- o.mathOps[i].dispose();
- thisChain = i;
- // assume nextChain is output gain node unless...
- if (thisChain < o.mathOps.length - 2) {
- nextChain = o.mathOps[i + 1];
- }
- }
- }
- if (thisChain == o.mathOps.length - 1) {
- o.mathOps.push(nextChain);
- }
- // assume source is the oscillator unless i > 0
- if (i > 0) {
- chainSource = o.mathOps[i - 1];
- }
- chainSource.disconnect();
- chainSource.connect(mathObj);
- mathObj.connect(nextChain);
- o.mathOps[thisChain] = mathObj;
- return o;
- };
- /**
- * Add a value to the p5.Oscillator's output amplitude,
- * and return the oscillator. Calling this method again
- * will override the initial add() with a new value.
- *
- * @method add
- * @param {Number} number Constant number to add
- * @return {p5.Oscillator} Oscillator Returns this oscillator
- * with scaled output
- *
- */
- p5.Oscillator.prototype.add = function (num) {
- var add = new Add(num);
- var thisChain = this.mathOps.length - 1;
- var nextChain = this.output;
- return sigChain(this, add, thisChain, nextChain, Add);
- };
- /**
- * Multiply the p5.Oscillator's output amplitude
- * by a fixed value (i.e. turn it up!). Calling this method
- * again will override the initial mult() with a new value.
- *
- * @method mult
- * @param {Number} number Constant number to multiply
- * @return {p5.Oscillator} Oscillator Returns this oscillator
- * with multiplied output
- */
- p5.Oscillator.prototype.mult = function (num) {
- var mult = new Mult(num);
- var thisChain = this.mathOps.length - 1;
- var nextChain = this.output;
- return sigChain(this, mult, thisChain, nextChain, Mult);
- };
- /**
- * Scale this oscillator's amplitude values to a given
- * range, and return the oscillator. Calling this method
- * again will override the initial scale() with new values.
- *
- * @method scale
- * @param {Number} inMin input range minumum
- * @param {Number} inMax input range maximum
- * @param {Number} outMin input range minumum
- * @param {Number} outMax input range maximum
- * @return {p5.Oscillator} Oscillator Returns this oscillator
- * with scaled output
- */
- p5.Oscillator.prototype.scale = function (inMin, inMax, outMin, outMax) {
- var mapOutMin, mapOutMax;
- if (arguments.length === 4) {
- mapOutMin = p5.prototype.map(outMin, inMin, inMax, 0, 1) - 0.5;
- mapOutMax = p5.prototype.map(outMax, inMin, inMax, 0, 1) - 0.5;
- } else {
- mapOutMin = arguments[0];
- mapOutMax = arguments[1];
- }
- var scale = new Scale(mapOutMin, mapOutMax);
- var thisChain = this.mathOps.length - 1;
- var nextChain = this.output;
- return sigChain(this, scale, thisChain, nextChain, Scale);
- };
- // ============================== //
- // SinOsc, TriOsc, SqrOsc, SawOsc //
- // ============================== //
- /**
- * Constructor: <code>new p5.SinOsc()</code>.
- * This creates a Sine Wave Oscillator and is
- * equivalent to <code> new p5.Oscillator('sine')
- * </code> or creating a p5.Oscillator and then calling
- * its method <code>setType('sine')</code>.
- * See p5.Oscillator for methods.
- *
- * @method p5.SinOsc
- * @param {Number} [freq] Set the frequency
- */
- p5.SinOsc = function (freq) {
- p5.Oscillator.call(this, freq, 'sine');
- };
- p5.SinOsc.prototype = Object.create(p5.Oscillator.prototype);
- /**
- * Constructor: <code>new p5.TriOsc()</code>.
- * This creates a Triangle Wave Oscillator and is
- * equivalent to <code>new p5.Oscillator('triangle')
- * </code> or creating a p5.Oscillator and then calling
- * its method <code>setType('triangle')</code>.
- * See p5.Oscillator for methods.
- *
- * @method p5.TriOsc
- * @param {Number} [freq] Set the frequency
- */
- p5.TriOsc = function (freq) {
- p5.Oscillator.call(this, freq, 'triangle');
- };
- p5.TriOsc.prototype = Object.create(p5.Oscillator.prototype);
- /**
- * Constructor: <code>new p5.SawOsc()</code>.
- * This creates a SawTooth Wave Oscillator and is
- * equivalent to <code> new p5.Oscillator('sawtooth')
- * </code> or creating a p5.Oscillator and then calling
- * its method <code>setType('sawtooth')</code>.
- * See p5.Oscillator for methods.
- *
- * @method p5.SawOsc
- * @param {Number} [freq] Set the frequency
- */
- p5.SawOsc = function (freq) {
- p5.Oscillator.call(this, freq, 'sawtooth');
- };
- p5.SawOsc.prototype = Object.create(p5.Oscillator.prototype);
- /**
- * Constructor: <code>new p5.SqrOsc()</code>.
- * This creates a Square Wave Oscillator and is
- * equivalent to <code> new p5.Oscillator('square')
- * </code> or creating a p5.Oscillator and then calling
- * its method <code>setType('square')</code>.
- * See p5.Oscillator for methods.
- *
- * @method p5.SqrOsc
- * @param {Number} [freq] Set the frequency
- */
- p5.SqrOsc = function (freq) {
- p5.Oscillator.call(this, freq, 'square');
- };
- p5.SqrOsc.prototype = Object.create(p5.Oscillator.prototype);
- }(master, Tone_signal_Signal, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_core_Timeline;
- Tone_core_Timeline = function (Tone) {
- 'use strict';
- Tone.Timeline = function () {
- var options = this.optionsObject(arguments, ['memory'], Tone.Timeline.defaults);
- this._timeline = [];
- this._toRemove = [];
- this._iterating = false;
- this.memory = options.memory;
- };
- Tone.extend(Tone.Timeline);
- Tone.Timeline.defaults = { 'memory': Infinity };
- Object.defineProperty(Tone.Timeline.prototype, 'length', {
- get: function () {
- return this._timeline.length;
- }
- });
- Tone.Timeline.prototype.addEvent = function (event) {
- if (this.isUndef(event.time)) {
- throw new Error('events must have a time attribute');
- }
- event.time = this.toSeconds(event.time);
- if (this._timeline.length) {
- var index = this._search(event.time);
- this._timeline.splice(index + 1, 0, event);
- } else {
- this._timeline.push(event);
- }
- if (this.length > this.memory) {
- var diff = this.length - this.memory;
- this._timeline.splice(0, diff);
- }
- return this;
- };
- Tone.Timeline.prototype.removeEvent = function (event) {
- if (this._iterating) {
- this._toRemove.push(event);
- } else {
- var index = this._timeline.indexOf(event);
- if (index !== -1) {
- this._timeline.splice(index, 1);
- }
- }
- return this;
- };
- Tone.Timeline.prototype.getEvent = function (time) {
- time = this.toSeconds(time);
- var index = this._search(time);
- if (index !== -1) {
- return this._timeline[index];
- } else {
- return null;
- }
- };
- Tone.Timeline.prototype.getEventAfter = function (time) {
- time = this.toSeconds(time);
- var index = this._search(time);
- if (index + 1 < this._timeline.length) {
- return this._timeline[index + 1];
- } else {
- return null;
- }
- };
- Tone.Timeline.prototype.getEventBefore = function (time) {
- time = this.toSeconds(time);
- var index = this._search(time);
- if (index - 1 >= 0) {
- return this._timeline[index - 1];
- } else {
- return null;
- }
- };
- Tone.Timeline.prototype.cancel = function (after) {
- if (this._timeline.length > 1) {
- after = this.toSeconds(after);
- var index = this._search(after);
- if (index >= 0) {
- this._timeline = this._timeline.slice(0, index);
- } else {
- this._timeline = [];
- }
- } else if (this._timeline.length === 1) {
- if (this._timeline[0].time >= after) {
- this._timeline = [];
- }
- }
- return this;
- };
- Tone.Timeline.prototype.cancelBefore = function (time) {
- if (this._timeline.length) {
- time = this.toSeconds(time);
- var index = this._search(time);
- if (index >= 0) {
- this._timeline = this._timeline.slice(index + 1);
- }
- }
- return this;
- };
- Tone.Timeline.prototype._search = function (time) {
- var beginning = 0;
- var len = this._timeline.length;
- var end = len;
- while (beginning <= end && beginning < len) {
- var midPoint = Math.floor(beginning + (end - beginning) / 2);
- var event = this._timeline[midPoint];
- if (event.time === time) {
- for (var i = midPoint; i < this._timeline.length; i++) {
- var testEvent = this._timeline[i];
- if (testEvent.time === time) {
- midPoint = i;
- }
- }
- return midPoint;
- } else if (event.time > time) {
- end = midPoint - 1;
- } else if (event.time < time) {
- beginning = midPoint + 1;
- }
- }
- return beginning - 1;
- };
- Tone.Timeline.prototype._iterate = function (callback, lowerBound, upperBound) {
- this._iterating = true;
- lowerBound = this.defaultArg(lowerBound, 0);
- upperBound = this.defaultArg(upperBound, this._timeline.length - 1);
- for (var i = lowerBound; i <= upperBound; i++) {
- callback(this._timeline[i]);
- }
- this._iterating = false;
- if (this._toRemove.length > 0) {
- for (var j = 0; j < this._toRemove.length; j++) {
- var index = this._timeline.indexOf(this._toRemove[j]);
- if (index !== -1) {
- this._timeline.splice(index, 1);
- }
- }
- this._toRemove = [];
- }
- };
- Tone.Timeline.prototype.forEach = function (callback) {
- this._iterate(callback);
- return this;
- };
- Tone.Timeline.prototype.forEachBefore = function (time, callback) {
- time = this.toSeconds(time);
- var upperBound = this._search(time);
- if (upperBound !== -1) {
- this._iterate(callback, 0, upperBound);
- }
- return this;
- };
- Tone.Timeline.prototype.forEachAfter = function (time, callback) {
- time = this.toSeconds(time);
- var lowerBound = this._search(time);
- this._iterate(callback, lowerBound + 1);
- return this;
- };
- Tone.Timeline.prototype.forEachFrom = function (time, callback) {
- time = this.toSeconds(time);
- var lowerBound = this._search(time);
- while (lowerBound >= 0 && this._timeline[lowerBound].time >= time) {
- lowerBound--;
- }
- this._iterate(callback, lowerBound + 1);
- return this;
- };
- Tone.Timeline.prototype.forEachAtTime = function (time, callback) {
- time = this.toSeconds(time);
- var upperBound = this._search(time);
- if (upperBound !== -1) {
- this._iterate(function (event) {
- if (event.time === time) {
- callback(event);
- }
- }, 0, upperBound);
- }
- return this;
- };
- Tone.Timeline.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._timeline = null;
- this._toRemove = null;
- };
- return Tone.Timeline;
- }(Tone_core_Tone);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_signal_TimelineSignal;
- Tone_signal_TimelineSignal = function (Tone) {
- 'use strict';
- Tone.TimelineSignal = function () {
- var options = this.optionsObject(arguments, [
- 'value',
- 'units'
- ], Tone.Signal.defaults);
- Tone.Signal.apply(this, options);
- options.param = this._param;
- Tone.Param.call(this, options);
- this._events = new Tone.Timeline(10);
- this._initial = this._fromUnits(this._param.value);
- };
- Tone.extend(Tone.TimelineSignal, Tone.Param);
- Tone.TimelineSignal.Type = {
- Linear: 'linear',
- Exponential: 'exponential',
- Target: 'target',
- Set: 'set'
- };
- Object.defineProperty(Tone.TimelineSignal.prototype, 'value', {
- get: function () {
- return this._toUnits(this._param.value);
- },
- set: function (value) {
- var convertedVal = this._fromUnits(value);
- this._initial = convertedVal;
- this._param.value = convertedVal;
- }
- });
- Tone.TimelineSignal.prototype.setValueAtTime = function (value, startTime) {
- value = this._fromUnits(value);
- startTime = this.toSeconds(startTime);
- this._events.addEvent({
- 'type': Tone.TimelineSignal.Type.Set,
- 'value': value,
- 'time': startTime
- });
- this._param.setValueAtTime(value, startTime);
- return this;
- };
- Tone.TimelineSignal.prototype.linearRampToValueAtTime = function (value, endTime) {
- value = this._fromUnits(value);
- endTime = this.toSeconds(endTime);
- this._events.addEvent({
- 'type': Tone.TimelineSignal.Type.Linear,
- 'value': value,
- 'time': endTime
- });
- this._param.linearRampToValueAtTime(value, endTime);
- return this;
- };
- Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) {
- value = this._fromUnits(value);
- value = Math.max(this._minOutput, value);
- endTime = this.toSeconds(endTime);
- this._events.addEvent({
- 'type': Tone.TimelineSignal.Type.Exponential,
- 'value': value,
- 'time': endTime
- });
- this._param.exponentialRampToValueAtTime(value, endTime);
- return this;
- };
- Tone.TimelineSignal.prototype.setTargetAtTime = function (value, startTime, timeConstant) {
- value = this._fromUnits(value);
- value = Math.max(this._minOutput, value);
- timeConstant = Math.max(this._minOutput, timeConstant);
- startTime = this.toSeconds(startTime);
- this._events.addEvent({
- 'type': Tone.TimelineSignal.Type.Target,
- 'value': value,
- 'time': startTime,
- 'constant': timeConstant
- });
- this._param.setTargetAtTime(value, startTime, timeConstant);
- return this;
- };
- Tone.TimelineSignal.prototype.cancelScheduledValues = function (after) {
- this._events.cancel(after);
- this._param.cancelScheduledValues(this.toSeconds(after));
- return this;
- };
- Tone.TimelineSignal.prototype.setRampPoint = function (time) {
- time = this.toSeconds(time);
- var val = this.getValueAtTime(time);
- var after = this._searchAfter(time);
- if (after) {
- this.cancelScheduledValues(time);
- if (after.type === Tone.TimelineSignal.Type.Linear) {
- this.linearRampToValueAtTime(val, time);
- } else if (after.type === Tone.TimelineSignal.Type.Exponential) {
- this.exponentialRampToValueAtTime(val, time);
- }
- }
- this.setValueAtTime(val, time);
- return this;
- };
- Tone.TimelineSignal.prototype.linearRampToValueBetween = function (value, start, finish) {
- this.setRampPoint(start);
- this.linearRampToValueAtTime(value, finish);
- return this;
- };
- Tone.TimelineSignal.prototype.exponentialRampToValueBetween = function (value, start, finish) {
- this.setRampPoint(start);
- this.exponentialRampToValueAtTime(value, finish);
- return this;
- };
- Tone.TimelineSignal.prototype._searchBefore = function (time) {
- return this._events.getEvent(time);
- };
- Tone.TimelineSignal.prototype._searchAfter = function (time) {
- return this._events.getEventAfter(time);
- };
- Tone.TimelineSignal.prototype.getValueAtTime = function (time) {
- var after = this._searchAfter(time);
- var before = this._searchBefore(time);
- var value = this._initial;
- if (before === null) {
- value = this._initial;
- } else if (before.type === Tone.TimelineSignal.Type.Target) {
- var previous = this._events.getEventBefore(before.time);
- var previouVal;
- if (previous === null) {
- previouVal = this._initial;
- } else {
- previouVal = previous.value;
- }
- value = this._exponentialApproach(before.time, previouVal, before.value, before.constant, time);
- } else if (after === null) {
- value = before.value;
- } else if (after.type === Tone.TimelineSignal.Type.Linear) {
- value = this._linearInterpolate(before.time, before.value, after.time, after.value, time);
- } else if (after.type === Tone.TimelineSignal.Type.Exponential) {
- value = this._exponentialInterpolate(before.time, before.value, after.time, after.value, time);
- } else {
- value = before.value;
- }
- return value;
- };
- Tone.TimelineSignal.prototype.connect = Tone.SignalBase.prototype.connect;
- Tone.TimelineSignal.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) {
- return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant);
- };
- Tone.TimelineSignal.prototype._linearInterpolate = function (t0, v0, t1, v1, t) {
- return v0 + (v1 - v0) * ((t - t0) / (t1 - t0));
- };
- Tone.TimelineSignal.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) {
- v0 = Math.max(this._minOutput, v0);
- return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
- };
- Tone.TimelineSignal.prototype.dispose = function () {
- Tone.Signal.prototype.dispose.call(this);
- Tone.Param.prototype.dispose.call(this);
- this._events.dispose();
- this._events = null;
- };
- return Tone.TimelineSignal;
- }(Tone_core_Tone, Tone_signal_Signal);
- var env;
- env = function () {
- 'use strict';
- var p5sound = master;
- var Add = Tone_signal_Add;
- var Mult = Tone_signal_Multiply;
- var Scale = Tone_signal_Scale;
- var TimelineSignal = Tone_signal_TimelineSignal;
- var Tone = Tone_core_Tone;
- Tone.setContext(p5sound.audiocontext);
- /**
- * <p>Envelopes are pre-defined amplitude distribution over time.
- * Typically, envelopes are used to control the output volume
- * of an object, a series of fades referred to as Attack, Decay,
- * Sustain and Release (
- * <a href="https://upload.wikimedia.org/wikipedia/commons/e/ea/ADSR_parameter.svg">ADSR</a>
- * ). Envelopes can also control other Web Audio Parameters—for example, a p5.Env can
- * control an Oscillator's frequency like this: <code>osc.freq(env)</code>.</p>
- * <p>Use <code><a href="#/p5.Env/setRange">setRange</a></code> to change the attack/release level.
- * Use <code><a href="#/p5.Env/setADSR">setADSR</a></code> to change attackTime, decayTime, sustainPercent and releaseTime.</p>
- * <p>Use the <code><a href="#/p5.Env/play">play</a></code> method to play the entire envelope,
- * the <code><a href="#/p5.Env/ramp">ramp</a></code> method for a pingable trigger,
- * or <code><a href="#/p5.Env/triggerAttack">triggerAttack</a></code>/
- * <code><a href="#/p5.Env/triggerRelease">triggerRelease</a></code> to trigger noteOn/noteOff.</p>
- *
- * @class p5.Env
- * @constructor
- * @example
- * <div><code>
- * var attackLevel = 1.0;
- * var releaseLevel = 0;
- *
- * var attackTime = 0.001
- * var decayTime = 0.2;
- * var susPercent = 0.2;
- * var releaseTime = 0.5;
- *
- * var env, triOsc;
- *
- * function setup() {
- * var cnv = createCanvas(100, 100);
- *
- * textAlign(CENTER);
- * text('click to play', width/2, height/2);
- *
- * env = new p5.Env();
- * env.setADSR(attackTime, decayTime, susPercent, releaseTime);
- * env.setRange(attackLevel, releaseLevel);
- *
- * triOsc = new p5.Oscillator('triangle');
- * triOsc.amp(env);
- * triOsc.start();
- * triOsc.freq(220);
- *
- * cnv.mousePressed(playEnv);
- * }
- *
- * function playEnv(){
- * env.play();
- * }
- * </code></div>
- */
- p5.Env = function (t1, l1, t2, l2, t3, l3) {
- var now = p5sound.audiocontext.currentTime;
- /**
- * Time until envelope reaches attackLevel
- * @property attackTime
- */
- this.aTime = t1 || 0.1;
- /**
- * Level once attack is complete.
- * @property attackLevel
- */
- this.aLevel = l1 || 1;
- /**
- * Time until envelope reaches decayLevel.
- * @property decayTime
- */
- this.dTime = t2 || 0.5;
- /**
- * Level after decay. The envelope will sustain here until it is released.
- * @property decayLevel
- */
- this.dLevel = l2 || 0;
- /**
- * Duration of the release portion of the envelope.
- * @property releaseTime
- */
- this.rTime = t3 || 0;
- /**
- * Level at the end of the release.
- * @property releaseLevel
- */
- this.rLevel = l3 || 0;
- this._rampHighPercentage = 0.98;
- this._rampLowPercentage = 0.02;
- this.output = p5sound.audiocontext.createGain();
- this.control = new TimelineSignal();
- this._init();
- // this makes sure the envelope starts at zero
- this.control.connect(this.output);
- // connect to the output
- this.connection = null;
- // store connection
- //array of math operation signal chaining
- this.mathOps = [this.control];
- //whether envelope should be linear or exponential curve
- this.isExponential = false;
- // oscillator or buffer source to clear on env complete
- // to save resources if/when it is retriggered
- this.sourceToClear = null;
- // set to true if attack is set, then false on release
- this.wasTriggered = false;
- // add to the soundArray so we can dispose of the env later
- p5sound.soundArray.push(this);
- };
- // this init function just smooths the starting value to zero and gives a start point for the timeline
- // - it was necessary to remove glitches at the beginning.
- p5.Env.prototype._init = function () {
- var now = p5sound.audiocontext.currentTime;
- var t = now;
- this.control.setTargetAtTime(0.00001, t, 0.001);
- //also, compute the correct time constants
- this._setRampAD(this.aTime, this.dTime);
- };
- /**
- * Reset the envelope with a series of time/value pairs.
- *
- * @method set
- * @param {Number} attackTime Time (in seconds) before level
- * reaches attackLevel
- * @param {Number} attackLevel Typically an amplitude between
- * 0.0 and 1.0
- * @param {Number} decayTime Time
- * @param {Number} decayLevel Amplitude (In a standard ADSR envelope,
- * decayLevel = sustainLevel)
- * @param {Number} releaseTime Release Time (in seconds)
- * @param {Number} releaseLevel Amplitude
- * @example
- * <div><code>
- * var t1 = 0.1; // attack time in seconds
- * var l1 = 0.7; // attack level 0.0 to 1.0
- * var t2 = 0.3; // decay time in seconds
- * var l2 = 0.1; // decay level 0.0 to 1.0
- * var t3 = 0.2; // sustain time in seconds
- * var l3 = dL; // sustain level 0.0 to 1.0
- * // release level defaults to zero
- *
- * var env;
- * var triOsc;
- *
- * function setup() {
- * background(0);
- * noStroke();
- * fill(255);
- * textAlign(CENTER);
- * text('click to play', width/2, height/2);
- *
- * env = new p5.Env(t1, l1, t2, l2, t3, l3);
- * triOsc = new p5.Oscillator('triangle');
- * triOsc.amp(env); // give the env control of the triOsc's amp
- * triOsc.start();
- * }
- *
- * // mouseClick triggers envelope if over canvas
- * function mouseClicked() {
- * // is mouse over canvas?
- * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
- * env.play(triOsc);
- * }
- * }
- * </code></div>
- *
- */
- p5.Env.prototype.set = function (t1, l1, t2, l2, t3, l3) {
- this.aTime = t1;
- this.aLevel = l1;
- this.dTime = t2 || 0;
- this.dLevel = l2 || 0;
- this.rTime = t3 || 0;
- this.rLevel = l3 || 0;
- // set time constants for ramp
- this._setRampAD(t1, t2);
- };
- /**
- * Set values like a traditional
- * <a href="https://en.wikipedia.org/wiki/Synthesizer#/media/File:ADSR_parameter.svg">
- * ADSR envelope
- * </a>.
- *
- * @method setADSR
- * @param {Number} attackTime Time (in seconds before envelope
- * reaches Attack Level
- * @param {Number} [decayTime] Time (in seconds) before envelope
- * reaches Decay/Sustain Level
- * @param {Number} [susRatio] Ratio between attackLevel and releaseLevel, on a scale from 0 to 1,
- * where 1.0 = attackLevel, 0.0 = releaseLevel.
- * The susRatio determines the decayLevel and the level at which the
- * sustain portion of the envelope will sustain.
- * For example, if attackLevel is 0.4, releaseLevel is 0,
- * and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is
- * increased to 1.0 (using <code>setRange</code>),
- * then decayLevel would increase proportionally, to become 0.5.
- * @param {Number} [releaseTime] Time in seconds from now (defaults to 0)
- * @example
- * <div><code>
- * var attackLevel = 1.0;
- * var releaseLevel = 0;
- *
- * var attackTime = 0.001
- * var decayTime = 0.2;
- * var susPercent = 0.2;
- * var releaseTime = 0.5;
- *
- * var env, triOsc;
- *
- * function setup() {
- * var cnv = createCanvas(100, 100);
- *
- * textAlign(CENTER);
- * text('click to play', width/2, height/2);
- *
- * env = new p5.Env();
- * env.setADSR(attackTime, decayTime, susPercent, releaseTime);
- * env.setRange(attackLevel, releaseLevel);
- *
- * triOsc = new p5.Oscillator('triangle');
- * triOsc.amp(env);
- * triOsc.start();
- * triOsc.freq(220);
- *
- * cnv.mousePressed(playEnv);
- * }
- *
- * function playEnv(){
- * env.play();
- * }
- * </code></div>
- */
- p5.Env.prototype.setADSR = function (aTime, dTime, sPercent, rTime) {
- this.aTime = aTime;
- this.dTime = dTime || 0;
- // lerp
- this.sPercent = sPercent || 0;
- this.dLevel = typeof sPercent !== 'undefined' ? sPercent * (this.aLevel - this.rLevel) + this.rLevel : 0;
- this.rTime = rTime || 0;
- // also set time constants for ramp
- this._setRampAD(aTime, dTime);
- };
- /**
- * Set max (attackLevel) and min (releaseLevel) of envelope.
- *
- * @method setRange
- * @param {Number} aLevel attack level (defaults to 1)
- * @param {Number} rLevel release level (defaults to 0)
- * @example
- * <div><code>
- * var attackLevel = 1.0;
- * var releaseLevel = 0;
- *
- * var attackTime = 0.001
- * var decayTime = 0.2;
- * var susPercent = 0.2;
- * var releaseTime = 0.5;
- *
- * var env, triOsc;
- *
- * function setup() {
- * var cnv = createCanvas(100, 100);
- *
- * textAlign(CENTER);
- * text('click to play', width/2, height/2);
- *
- * env = new p5.Env();
- * env.setADSR(attackTime, decayTime, susPercent, releaseTime);
- * env.setRange(attackLevel, releaseLevel);
- *
- * triOsc = new p5.Oscillator('triangle');
- * triOsc.amp(env);
- * triOsc.start();
- * triOsc.freq(220);
- *
- * cnv.mousePressed(playEnv);
- * }
- *
- * function playEnv(){
- * env.play();
- * }
- * </code></div>
- */
- p5.Env.prototype.setRange = function (aLevel, rLevel) {
- this.aLevel = aLevel || 1;
- this.rLevel = rLevel || 0;
- };
- // private (undocumented) method called when ADSR is set to set time constants for ramp
- //
- // Set the <a href="https://en.wikipedia.org/wiki/RC_time_constant">
- // time constants</a> for simple exponential ramps.
- // The larger the time constant value, the slower the
- // transition will be.
- //
- // method _setRampAD
- // param {Number} attackTimeConstant attack time constant
- // param {Number} decayTimeConstant decay time constant
- //
- p5.Env.prototype._setRampAD = function (t1, t2) {
- this._rampAttackTime = this.checkExpInput(t1);
- this._rampDecayTime = this.checkExpInput(t2);
- var TCDenominator = 1;
- /// Aatish Bhatia's calculation for time constant for rise(to adjust 1/1-e calculation to any percentage)
- TCDenominator = Math.log(1 / this.checkExpInput(1 - this._rampHighPercentage));
- this._rampAttackTC = t1 / this.checkExpInput(TCDenominator);
- TCDenominator = Math.log(1 / this._rampLowPercentage);
- this._rampDecayTC = t2 / this.checkExpInput(TCDenominator);
- };
- // private method
- p5.Env.prototype.setRampPercentages = function (p1, p2) {
- //set the percentages that the simple exponential ramps go to
- this._rampHighPercentage = this.checkExpInput(p1);
- this._rampLowPercentage = this.checkExpInput(p2);
- var TCDenominator = 1;
- //now re-compute the time constants based on those percentages
- /// Aatish Bhatia's calculation for time constant for rise(to adjust 1/1-e calculation to any percentage)
- TCDenominator = Math.log(1 / this.checkExpInput(1 - this._rampHighPercentage));
- this._rampAttackTC = this._rampAttackTime / this.checkExpInput(TCDenominator);
- TCDenominator = Math.log(1 / this._rampLowPercentage);
- this._rampDecayTC = this._rampDecayTime / this.checkExpInput(TCDenominator);
- };
- /**
- * Assign a parameter to be controlled by this envelope.
- * If a p5.Sound object is given, then the p5.Env will control its
- * output gain. If multiple inputs are provided, the env will
- * control all of them.
- *
- * @method setInput
- * @param {Object} unit A p5.sound object or
- * Web Audio Param.
- */
- p5.Env.prototype.setInput = function (unit) {
- for (var i = 0; i < arguments.length; i++) {
- this.connect(arguments[i]);
- }
- };
- /**
- * Set whether the envelope ramp is linear (default) or exponential.
- * Exponential ramps can be useful because we perceive amplitude
- * and frequency logarithmically.
- *
- * @method setExp
- * @param {Boolean} isExp true is exponential, false is linear
- */
- p5.Env.prototype.setExp = function (isExp) {
- this.isExponential = isExp;
- };
- //helper method to protect against zero values being sent to exponential functions
- p5.Env.prototype.checkExpInput = function (value) {
- if (value <= 0) {
- value = 1e-8;
- }
- return value;
- };
- /**
- * Play tells the envelope to start acting on a given input.
- * If the input is a p5.sound object (i.e. AudioIn, Oscillator,
- * SoundFile), then Env will control its output volume.
- * Envelopes can also be used to control any <a href="
- * http://docs.webplatform.org/wiki/apis/webaudio/AudioParam">
- * Web Audio Audio Param.</a>
- *
- * @method play
- * @param {Object} unit A p5.sound object or
- * Web Audio Param.
- * @param {Number} [startTime] time from now (in seconds) at which to play
- * @param {Number} [sustainTime] time to sustain before releasing the envelope
- * @example
- * <div><code>
- * var attackLevel = 1.0;
- * var releaseLevel = 0;
- *
- * var attackTime = 0.001
- * var decayTime = 0.2;
- * var susPercent = 0.2;
- * var releaseTime = 0.5;
- *
- * var env, triOsc;
- *
- * function setup() {
- * var cnv = createCanvas(100, 100);
- *
- * textAlign(CENTER);
- * text('click to play', width/2, height/2);
- *
- * env = new p5.Env();
- * env.setADSR(attackTime, decayTime, susPercent, releaseTime);
- * env.setRange(attackLevel, releaseLevel);
- *
- * triOsc = new p5.Oscillator('triangle');
- * triOsc.amp(env);
- * triOsc.start();
- * triOsc.freq(220);
- *
- * cnv.mousePressed(playEnv);
- * }
- *
- * function playEnv(){
- * // trigger env on triOsc, 0 seconds from now
- * // After decay, sustain for 0.2 seconds before release
- * env.play(triOsc, 0, 0.2);
- * }
- * </code></div>
- */
- p5.Env.prototype.play = function (unit, secondsFromNow, susTime) {
- var now = p5sound.audiocontext.currentTime;
- var tFromNow = secondsFromNow || 0;
- var susTime = susTime || 0;
- if (unit) {
- if (this.connection !== unit) {
- this.connect(unit);
- }
- }
- this.triggerAttack(unit, tFromNow);
- this.triggerRelease(unit, tFromNow + this.aTime + this.dTime + susTime);
- };
- /**
- * Trigger the Attack, and Decay portion of the Envelope.
- * Similar to holding down a key on a piano, but it will
- * hold the sustain level until you let go. Input can be
- * any p5.sound object, or a <a href="
- * http://docs.webplatform.org/wiki/apis/webaudio/AudioParam">
- * Web Audio Param</a>.
- *
- * @method triggerAttack
- * @param {Object} unit p5.sound Object or Web Audio Param
- * @param {Number} secondsFromNow time from now (in seconds)
- * @example
- * <div><code>
- *
- * var attackLevel = 1.0;
- * var releaseLevel = 0;
- *
- * var attackTime = 0.001
- * var decayTime = 0.3;
- * var susPercent = 0.4;
- * var releaseTime = 0.5;
- *
- * var env, triOsc;
- *
- * function setup() {
- * var cnv = createCanvas(100, 100);
- * background(200);
- * textAlign(CENTER);
- * text('click to play', width/2, height/2);
- *
- * env = new p5.Env();
- * env.setADSR(attackTime, decayTime, susPercent, releaseTime);
- * env.setRange(attackLevel, releaseLevel);
- *
- * triOsc = new p5.Oscillator('triangle');
- * triOsc.amp(env);
- * triOsc.start();
- * triOsc.freq(220);
- *
- * cnv.mousePressed(envAttack);
- * }
- *
- * function envAttack(){
- * console.log('trigger attack');
- * env.triggerAttack();
- *
- * background(0,255,0);
- * text('attack!', width/2, height/2);
- * }
- *
- * function mouseReleased() {
- * env.triggerRelease();
- *
- * background(200);
- * text('click to play', width/2, height/2);
- * }
- * </code></div>
- */
- p5.Env.prototype.triggerAttack = function (unit, secondsFromNow) {
- var now = p5sound.audiocontext.currentTime;
- var tFromNow = secondsFromNow || 0;
- var t = now + tFromNow;
- this.lastAttack = t;
- this.wasTriggered = true;
- if (unit) {
- if (this.connection !== unit) {
- this.connect(unit);
- }
- }
- // get and set value (with linear ramp) to anchor automation
- var valToSet = this.control.getValueAtTime(t);
- this.control.cancelScheduledValues(t);
- // not sure if this is necessary
- if (this.isExponential == true) {
- this.control.exponentialRampToValueAtTime(this.checkExpInput(valToSet), t);
- } else {
- this.control.linearRampToValueAtTime(valToSet, t);
- }
- // after each ramp completes, cancel scheduled values
- // (so they can be overridden in case env has been re-triggered)
- // then, set current value (with linearRamp to avoid click)
- // then, schedule the next automation...
- // attack
- t += this.aTime;
- if (this.isExponential == true) {
- this.control.exponentialRampToValueAtTime(this.checkExpInput(this.aLevel), t);
- valToSet = this.checkExpInput(this.control.getValueAtTime(t));
- this.control.cancelScheduledValues(t);
- this.control.exponentialRampToValueAtTime(valToSet, t);
- } else {
- this.control.linearRampToValueAtTime(this.aLevel, t);
- valToSet = this.control.getValueAtTime(t);
- this.control.cancelScheduledValues(t);
- this.control.linearRampToValueAtTime(valToSet, t);
- }
- // decay to decay level (if using ADSR, then decay level == sustain level)
- t += this.dTime;
- if (this.isExponential == true) {
- this.control.exponentialRampToValueAtTime(this.checkExpInput(this.dLevel), t);
- valToSet = this.checkExpInput(this.control.getValueAtTime(t));
- this.control.cancelScheduledValues(t);
- this.control.exponentialRampToValueAtTime(valToSet, t);
- } else {
- this.control.linearRampToValueAtTime(this.dLevel, t);
- valToSet = this.control.getValueAtTime(t);
- this.control.cancelScheduledValues(t);
- this.control.linearRampToValueAtTime(valToSet, t);
- }
- };
- /**
- * Trigger the Release of the Envelope. This is similar to releasing
- * the key on a piano and letting the sound fade according to the
- * release level and release time.
- *
- * @method triggerRelease
- * @param {Object} unit p5.sound Object or Web Audio Param
- * @param {Number} secondsFromNow time to trigger the release
- * @example
- * <div><code>
- *
- * var attackLevel = 1.0;
- * var releaseLevel = 0;
- *
- * var attackTime = 0.001
- * var decayTime = 0.3;
- * var susPercent = 0.4;
- * var releaseTime = 0.5;
- *
- * var env, triOsc;
- *
- * function setup() {
- * var cnv = createCanvas(100, 100);
- * background(200);
- * textAlign(CENTER);
- * text('click to play', width/2, height/2);
- *
- * env = new p5.Env();
- * env.setADSR(attackTime, decayTime, susPercent, releaseTime);
- * env.setRange(attackLevel, releaseLevel);
- *
- * triOsc = new p5.Oscillator('triangle');
- * triOsc.amp(env);
- * triOsc.start();
- * triOsc.freq(220);
- *
- * cnv.mousePressed(envAttack);
- * }
- *
- * function envAttack(){
- * console.log('trigger attack');
- * env.triggerAttack();
- *
- * background(0,255,0);
- * text('attack!', width/2, height/2);
- * }
- *
- * function mouseReleased() {
- * env.triggerRelease();
- *
- * background(200);
- * text('click to play', width/2, height/2);
- * }
- * </code></div>
- */
- p5.Env.prototype.triggerRelease = function (unit, secondsFromNow) {
- // only trigger a release if an attack was triggered
- if (!this.wasTriggered) {
- // this currently causes a bit of trouble:
- // if a later release has been scheduled (via the play function)
- // a new earlier release won't interrupt it, because
- // this.wasTriggered has already been set to false.
- // If we want new earlier releases to override, then we need to
- // keep track of the last release time, and if the new release time is
- // earlier, then use it.
- return;
- }
- var now = p5sound.audiocontext.currentTime;
- var tFromNow = secondsFromNow || 0;
- var t = now + tFromNow;
- if (unit) {
- if (this.connection !== unit) {
- this.connect(unit);
- }
- }
- // get and set value (with linear or exponential ramp) to anchor automation
- var valToSet = this.control.getValueAtTime(t);
- this.control.cancelScheduledValues(t);
- // not sure if this is necessary
- if (this.isExponential == true) {
- this.control.exponentialRampToValueAtTime(this.checkExpInput(valToSet), t);
- } else {
- this.control.linearRampToValueAtTime(valToSet, t);
- }
- // release
- t += this.rTime;
- if (this.isExponential == true) {
- this.control.exponentialRampToValueAtTime(this.checkExpInput(this.rLevel), t);
- valToSet = this.checkExpInput(this.control.getValueAtTime(t));
- this.control.cancelScheduledValues(t);
- this.control.exponentialRampToValueAtTime(valToSet, t);
- } else {
- this.control.linearRampToValueAtTime(this.rLevel, t);
- valToSet = this.control.getValueAtTime(t);
- this.control.cancelScheduledValues(t);
- this.control.linearRampToValueAtTime(valToSet, t);
- }
- this.wasTriggered = false;
- };
- /**
- * Exponentially ramp to a value using the first two
- * values from <code><a href="#/p5.Env/setADSR">setADSR(attackTime, decayTime)</a></code>
- * as <a href="https://en.wikipedia.org/wiki/RC_time_constant">
- * time constants</a> for simple exponential ramps.
- * If the value is higher than current value, it uses attackTime,
- * while a decrease uses decayTime.
- *
- * @method ramp
- * @param {Object} unit p5.sound Object or Web Audio Param
- * @param {Number} secondsFromNow When to trigger the ramp
- * @param {Number} v Target value
- * @param {Number} [v2] Second target value (optional)
- * @example
- * <div><code>
- * var env, osc, amp, cnv;
- *
- * var attackTime = 0.001;
- * var decayTime = 0.2;
- * var attackLevel = 1;
- * var decayLevel = 0;
- *
- * function setup() {
- * cnv = createCanvas(100, 100);
- * fill(0,255,0);
- * noStroke();
- *
- * env = new p5.Env();
- * env.setADSR(attackTime, decayTime);
- *
- * osc = new p5.Oscillator();
- * osc.amp(env);
- * osc.start();
- *
- * amp = new p5.Amplitude();
- *
- * cnv.mousePressed(triggerRamp);
- * }
- *
- * function triggerRamp() {
- * env.ramp(osc, 0, attackLevel, decayLevel);
- * }
- *
- * function draw() {
- * background(20,20,20);
- * text('click me', 10, 20);
- * var h = map(amp.getLevel(), 0, 0.4, 0, height);;
- *
- * rect(0, height, width, -h);
- * }
- * </code></div>
- */
- p5.Env.prototype.ramp = function (unit, secondsFromNow, v1, v2) {
- var now = p5sound.audiocontext.currentTime;
- var tFromNow = secondsFromNow || 0;
- var t = now + tFromNow;
- var destination1 = this.checkExpInput(v1);
- var destination2 = typeof v2 !== 'undefined' ? this.checkExpInput(v2) : undefined;
- // connect env to unit if not already connected
- if (unit) {
- if (this.connection !== unit) {
- this.connect(unit);
- }
- }
- //get current value
- var currentVal = this.checkExpInput(this.control.getValueAtTime(t));
- this.control.cancelScheduledValues(t);
- //if it's going up
- if (destination1 > currentVal) {
- this.control.setTargetAtTime(destination1, t, this._rampAttackTC);
- t += this._rampAttackTime;
- } else if (destination1 < currentVal) {
- this.control.setTargetAtTime(destination1, t, this._rampDecayTC);
- t += this._rampDecayTime;
- }
- // Now the second part of envelope begins
- if (destination2 === undefined)
- return;
- //if it's going up
- if (destination2 > destination1) {
- this.control.setTargetAtTime(destination2, t, this._rampAttackTC);
- } else if (destination2 < destination1) {
- this.control.setTargetAtTime(destination2, t, this._rampDecayTC);
- }
- };
- p5.Env.prototype.connect = function (unit) {
- this.connection = unit;
- // assume we're talking about output gain
- // unless given a different audio param
- if (unit instanceof p5.Oscillator || unit instanceof p5.SoundFile || unit instanceof p5.AudioIn || unit instanceof p5.Reverb || unit instanceof p5.Noise || unit instanceof p5.Filter || unit instanceof p5.Delay) {
- unit = unit.output.gain;
- }
- if (unit instanceof AudioParam) {
- //set the initial value
- unit.setValueAtTime(0, p5sound.audiocontext.currentTime);
- }
- if (unit instanceof p5.Signal) {
- unit.setValue(0);
- }
- this.output.connect(unit);
- };
- p5.Env.prototype.disconnect = function (unit) {
- this.output.disconnect();
- };
- // Signal Math
- /**
- * Add a value to the p5.Oscillator's output amplitude,
- * and return the oscillator. Calling this method
- * again will override the initial add() with new values.
- *
- * @method add
- * @param {Number} number Constant number to add
- * @return {p5.Env} Envelope Returns this envelope
- * with scaled output
- */
- p5.Env.prototype.add = function (num) {
- var add = new Add(num);
- var thisChain = this.mathOps.length;
- var nextChain = this.output;
- return p5.prototype._mathChain(this, add, thisChain, nextChain, Add);
- };
- /**
- * Multiply the p5.Env's output amplitude
- * by a fixed value. Calling this method
- * again will override the initial mult() with new values.
- *
- * @method mult
- * @param {Number} number Constant number to multiply
- * @return {p5.Env} Envelope Returns this envelope
- * with scaled output
- */
- p5.Env.prototype.mult = function (num) {
- var mult = new Mult(num);
- var thisChain = this.mathOps.length;
- var nextChain = this.output;
- return p5.prototype._mathChain(this, mult, thisChain, nextChain, Mult);
- };
- /**
- * Scale this envelope's amplitude values to a given
- * range, and return the envelope. Calling this method
- * again will override the initial scale() with new values.
- *
- * @method scale
- * @param {Number} inMin input range minumum
- * @param {Number} inMax input range maximum
- * @param {Number} outMin input range minumum
- * @param {Number} outMax input range maximum
- * @return {p5.Env} Envelope Returns this envelope
- * with scaled output
- */
- p5.Env.prototype.scale = function (inMin, inMax, outMin, outMax) {
- var scale = new Scale(inMin, inMax, outMin, outMax);
- var thisChain = this.mathOps.length;
- var nextChain = this.output;
- return p5.prototype._mathChain(this, scale, thisChain, nextChain, Scale);
- };
- // get rid of the oscillator
- p5.Env.prototype.dispose = function () {
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- var now = p5sound.audiocontext.currentTime;
- this.disconnect();
- try {
- this.control.dispose();
- this.control = null;
- } catch (e) {
- }
- for (var i = 1; i < this.mathOps.length; i++) {
- mathOps[i].dispose();
- }
- };
- }(master, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_signal_TimelineSignal, Tone_core_Tone);
- var pulse;
- pulse = function () {
- 'use strict';
- var p5sound = master;
- /**
- * Creates a Pulse object, an oscillator that implements
- * Pulse Width Modulation.
- * The pulse is created with two oscillators.
- * Accepts a parameter for frequency, and to set the
- * width between the pulses. See <a href="
- * http://p5js.org/reference/#/p5.Oscillator">
- * <code>p5.Oscillator</code> for a full list of methods.
- *
- * @class p5.Pulse
- * @constructor
- * @param {Number} [freq] Frequency in oscillations per second (Hz)
- * @param {Number} [w] Width between the pulses (0 to 1.0,
- * defaults to 0)
- * @example
- * <div><code>
- * var pulse;
- * function setup() {
- * background(0);
- *
- * // Create and start the pulse wave oscillator
- * pulse = new p5.Pulse();
- * pulse.amp(0.5);
- * pulse.freq(220);
- * pulse.start();
- * }
- *
- * function draw() {
- * var w = map(mouseX, 0, width, 0, 1);
- * w = constrain(w, 0, 1);
- * pulse.width(w)
- * }
- * </code></div>
- */
- p5.Pulse = function (freq, w) {
- p5.Oscillator.call(this, freq, 'sawtooth');
- // width of PWM, should be betw 0 to 1.0
- this.w = w || 0;
- // create a second oscillator with inverse frequency
- this.osc2 = new p5.SawOsc(freq);
- // create a delay node
- this.dNode = p5sound.audiocontext.createDelay();
- // dc offset
- this.dcOffset = createDCOffset();
- this.dcGain = p5sound.audiocontext.createGain();
- this.dcOffset.connect(this.dcGain);
- this.dcGain.connect(this.output);
- // set delay time based on PWM width
- this.f = freq || 440;
- var mW = this.w / this.oscillator.frequency.value;
- this.dNode.delayTime.value = mW;
- this.dcGain.gain.value = 1.7 * (0.5 - this.w);
- // disconnect osc2 and connect it to delay, which is connected to output
- this.osc2.disconnect();
- this.osc2.panner.disconnect();
- this.osc2.amp(-1);
- // inverted amplitude
- this.osc2.output.connect(this.dNode);
- this.dNode.connect(this.output);
- this.output.gain.value = 1;
- this.output.connect(this.panner);
- };
- p5.Pulse.prototype = Object.create(p5.Oscillator.prototype);
- /**
- * Set the width of a Pulse object (an oscillator that implements
- * Pulse Width Modulation).
- *
- * @method width
- * @param {Number} [width] Width between the pulses (0 to 1.0,
- * defaults to 0)
- */
- p5.Pulse.prototype.width = function (w) {
- if (typeof w === 'number') {
- if (w <= 1 && w >= 0) {
- this.w = w;
- // set delay time based on PWM width
- // var mW = map(this.w, 0, 1.0, 0, 1/this.f);
- var mW = this.w / this.oscillator.frequency.value;
- this.dNode.delayTime.value = mW;
- }
- this.dcGain.gain.value = 1.7 * (0.5 - this.w);
- } else {
- w.connect(this.dNode.delayTime);
- var sig = new p5.SignalAdd(-0.5);
- sig.setInput(w);
- sig = sig.mult(-1);
- sig = sig.mult(1.7);
- sig.connect(this.dcGain.gain);
- }
- };
- p5.Pulse.prototype.start = function (f, time) {
- var now = p5sound.audiocontext.currentTime;
- var t = time || 0;
- if (!this.started) {
- var freq = f || this.f;
- var type = this.oscillator.type;
- this.oscillator = p5sound.audiocontext.createOscillator();
- this.oscillator.frequency.setValueAtTime(freq, now);
- this.oscillator.type = type;
- this.oscillator.connect(this.output);
- this.oscillator.start(t + now);
- // set up osc2
- this.osc2.oscillator = p5sound.audiocontext.createOscillator();
- this.osc2.oscillator.frequency.setValueAtTime(freq, t + now);
- this.osc2.oscillator.type = type;
- this.osc2.oscillator.connect(this.osc2.output);
- this.osc2.start(t + now);
- this.freqNode = [
- this.oscillator.frequency,
- this.osc2.oscillator.frequency
- ];
- // start dcOffset, too
- this.dcOffset = createDCOffset();
- this.dcOffset.connect(this.dcGain);
- this.dcOffset.start(t + now);
- // if LFO connections depend on these oscillators
- if (this.mods !== undefined && this.mods.frequency !== undefined) {
- this.mods.frequency.connect(this.freqNode[0]);
- this.mods.frequency.connect(this.freqNode[1]);
- }
- this.started = true;
- this.osc2.started = true;
- }
- };
- p5.Pulse.prototype.stop = function (time) {
- if (this.started) {
- var t = time || 0;
- var now = p5sound.audiocontext.currentTime;
- this.oscillator.stop(t + now);
- this.osc2.oscillator.stop(t + now);
- this.dcOffset.stop(t + now);
- this.started = false;
- this.osc2.started = false;
- }
- };
- p5.Pulse.prototype.freq = function (val, rampTime, tFromNow) {
- if (typeof val === 'number') {
- this.f = val;
- var now = p5sound.audiocontext.currentTime;
- var rampTime = rampTime || 0;
- var tFromNow = tFromNow || 0;
- var currentFreq = this.oscillator.frequency.value;
- this.oscillator.frequency.cancelScheduledValues(now);
- this.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow);
- this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now);
- this.osc2.oscillator.frequency.cancelScheduledValues(now);
- this.osc2.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow);
- this.osc2.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now);
- if (this.freqMod) {
- this.freqMod.output.disconnect();
- this.freqMod = null;
- }
- } else if (val.output) {
- val.output.disconnect();
- val.output.connect(this.oscillator.frequency);
- val.output.connect(this.osc2.oscillator.frequency);
- this.freqMod = val;
- }
- };
- // inspiration: http://webaudiodemos.appspot.com/oscilloscope/
- function createDCOffset() {
- var ac = p5sound.audiocontext;
- var buffer = ac.createBuffer(1, 2048, ac.sampleRate);
- var data = buffer.getChannelData(0);
- for (var i = 0; i < 2048; i++)
- data[i] = 1;
- var bufferSource = ac.createBufferSource();
- bufferSource.buffer = buffer;
- bufferSource.loop = true;
- return bufferSource;
- }
- }(master, oscillator);
- var noise;
- noise = function () {
- 'use strict';
- var p5sound = master;
- /**
- * Noise is a type of oscillator that generates a buffer with random values.
- *
- * @class p5.Noise
- * @constructor
- * @param {String} type Type of noise can be 'white' (default),
- * 'brown' or 'pink'.
- * @return {Object} Noise Object
- */
- p5.Noise = function (type) {
- var assignType;
- p5.Oscillator.call(this);
- delete this.f;
- delete this.freq;
- delete this.oscillator;
- if (type === 'brown') {
- assignType = _brownNoise;
- } else if (type === 'pink') {
- assignType = _pinkNoise;
- } else {
- assignType = _whiteNoise;
- }
- this.buffer = assignType;
- };
- p5.Noise.prototype = Object.create(p5.Oscillator.prototype);
- // generate noise buffers
- var _whiteNoise = function () {
- var bufferSize = 2 * p5sound.audiocontext.sampleRate;
- var whiteBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate);
- var noiseData = whiteBuffer.getChannelData(0);
- for (var i = 0; i < bufferSize; i++) {
- noiseData[i] = Math.random() * 2 - 1;
- }
- whiteBuffer.type = 'white';
- return whiteBuffer;
- }();
- var _pinkNoise = function () {
- var bufferSize = 2 * p5sound.audiocontext.sampleRate;
- var pinkBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate);
- var noiseData = pinkBuffer.getChannelData(0);
- var b0, b1, b2, b3, b4, b5, b6;
- b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0;
- for (var i = 0; i < bufferSize; i++) {
- var white = Math.random() * 2 - 1;
- b0 = 0.99886 * b0 + white * 0.0555179;
- b1 = 0.99332 * b1 + white * 0.0750759;
- b2 = 0.969 * b2 + white * 0.153852;
- b3 = 0.8665 * b3 + white * 0.3104856;
- b4 = 0.55 * b4 + white * 0.5329522;
- b5 = -0.7616 * b5 - white * 0.016898;
- noiseData[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
- noiseData[i] *= 0.11;
- // (roughly) compensate for gain
- b6 = white * 0.115926;
- }
- pinkBuffer.type = 'pink';
- return pinkBuffer;
- }();
- var _brownNoise = function () {
- var bufferSize = 2 * p5sound.audiocontext.sampleRate;
- var brownBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate);
- var noiseData = brownBuffer.getChannelData(0);
- var lastOut = 0;
- for (var i = 0; i < bufferSize; i++) {
- var white = Math.random() * 2 - 1;
- noiseData[i] = (lastOut + 0.02 * white) / 1.02;
- lastOut = noiseData[i];
- noiseData[i] *= 3.5;
- }
- brownBuffer.type = 'brown';
- return brownBuffer;
- }();
- /**
- * Set type of noise to 'white', 'pink' or 'brown'.
- * White is the default.
- *
- * @method setType
- * @param {String} [type] 'white', 'pink' or 'brown'
- */
- p5.Noise.prototype.setType = function (type) {
- switch (type) {
- case 'white':
- this.buffer = _whiteNoise;
- break;
- case 'pink':
- this.buffer = _pinkNoise;
- break;
- case 'brown':
- this.buffer = _brownNoise;
- break;
- default:
- this.buffer = _whiteNoise;
- }
- if (this.started) {
- var now = p5sound.audiocontext.currentTime;
- this.stop(now);
- this.start(now + 0.01);
- }
- };
- p5.Noise.prototype.getType = function () {
- return this.buffer.type;
- };
- /**
- * Start the noise
- *
- * @method start
- */
- p5.Noise.prototype.start = function () {
- if (this.started) {
- this.stop();
- }
- this.noise = p5sound.audiocontext.createBufferSource();
- this.noise.buffer = this.buffer;
- this.noise.loop = true;
- this.noise.connect(this.output);
- var now = p5sound.audiocontext.currentTime;
- this.noise.start(now);
- this.started = true;
- };
- /**
- * Stop the noise.
- *
- * @method stop
- */
- p5.Noise.prototype.stop = function () {
- var now = p5sound.audiocontext.currentTime;
- if (this.noise) {
- this.noise.stop(now);
- this.started = false;
- }
- };
- /**
- * Pan the noise.
- *
- * @method pan
- * @param {Number} panning Number between -1 (left)
- * and 1 (right)
- * @param {Number} timeFromNow schedule this event to happen
- * seconds from now
- */
- /**
- * Set the amplitude of the noise between 0 and 1.0. Or,
- * modulate amplitude with an audio signal such as an oscillator.
- *
- * @param {Number|Object} volume amplitude between 0 and 1.0
- * or modulating signal/oscillator
- * @param {Number} [rampTime] create a fade that lasts rampTime
- * @param {Number} [timeFromNow] schedule this event to happen
- * seconds from now
- */
- /**
- * Send output to a p5.sound or web audio object
- *
- * @method connect
- * @param {Object} unit
- */
- /**
- * Disconnect all output.
- *
- * @method disconnect
- */
- p5.Noise.prototype.dispose = function () {
- var now = p5sound.audiocontext.currentTime;
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- if (this.noise) {
- this.noise.disconnect();
- this.stop(now);
- }
- if (this.output) {
- this.output.disconnect();
- }
- if (this.panner) {
- this.panner.disconnect();
- }
- this.output = null;
- this.panner = null;
- this.buffer = null;
- this.noise = null;
- };
- }(master);
- var audioin;
- audioin = function () {
- 'use strict';
- var p5sound = master;
- var CustomError = errorHandler;
- /**
- * <p>Get audio from an input, i.e. your computer's microphone.</p>
- *
- * <p>Turn the mic on/off with the start() and stop() methods. When the mic
- * is on, its volume can be measured with getLevel or by connecting an
- * FFT object.</p>
- *
- * <p>If you want to hear the AudioIn, use the .connect() method.
- * AudioIn does not connect to p5.sound output by default to prevent
- * feedback.</p>
- *
- * <p><em>Note: This uses the <a href="http://caniuse.com/stream">getUserMedia/
- * Stream</a> API, which is not supported by certain browsers. Access in Chrome browser
- * is limited to localhost and https, but access over http may be limited.</em></p>
- *
- * @class p5.AudioIn
- * @constructor
- * @param {Function} [errorCallback] A function to call if there is an error
- * accessing the AudioIn. For example,
- * Safari and iOS devices do not
- * currently allow microphone access.
- * @return {Object} AudioIn
- * @example
- * <div><code>
- * var mic;
- * function setup(){
- * mic = new p5.AudioIn()
- * mic.start();
- * }
- * function draw(){
- * background(0);
- * micLevel = mic.getLevel();
- * ellipse(width/2, constrain(height-micLevel*height*5, 0, height), 10, 10);
- * }
- * </code></div>
- */
- p5.AudioIn = function (errorCallback) {
- // set up audio input
- this.input = p5sound.audiocontext.createGain();
- this.output = p5sound.audiocontext.createGain();
- this.stream = null;
- this.mediaStream = null;
- this.currentSource = 0;
- /**
- * Client must allow browser to access their microphone / audioin source.
- * Default: false. Will become true when the client enables acces.
- *
- * @property {Boolean} enabled
- */
- this.enabled = false;
- // create an amplitude, connect to it by default but not to master out
- this.amplitude = new p5.Amplitude();
- this.output.connect(this.amplitude.input);
- // Some browsers let developer determine their input sources
- if (typeof window.MediaStreamTrack === 'undefined') {
- if (errorCallback) {
- errorCallback();
- } else {
- window.alert('This browser does not support AudioIn');
- }
- } else if (typeof window.MediaDevices.enumerateDevices === 'function') {
- // Chrome supports getSources to list inputs. Dev picks default
- window.MediaDevices.enumerateDevices(this._gotSources);
- } else {
- }
- // add to soundArray so we can dispose on close
- p5sound.soundArray.push(this);
- };
- /**
- * Start processing audio input. This enables the use of other
- * AudioIn methods like getLevel(). Note that by default, AudioIn
- * is not connected to p5.sound's output. So you won't hear
- * anything unless you use the connect() method.<br/>
- *
- * Certain browsers limit access to the user's microphone. For example,
- * Chrome only allows access from localhost and over https. For this reason,
- * you may want to include an errorCallback—a function that is called in case
- * the browser won't provide mic access.
- *
- * @method start
- * @param {Function} successCallback Name of a function to call on
- * success.
- * @param {Function} errorCallback Name of a function to call if
- * there was an error. For example,
- * some browsers do not support
- * getUserMedia.
- */
- p5.AudioIn.prototype.start = function (successCallback, errorCallback) {
- var self = this;
- // if stream was already started...
- // if _gotSources() i.e. developers determine which source to use
- if (p5sound.inputSources[self.currentSource]) {
- // set the audio source
- var audioSource = p5sound.inputSources[self.currentSource].id;
- var constraints = { audio: { optional: [{ sourceId: audioSource }] } };
- window.navigator.getUserMedia(constraints, this._onStream = function (stream) {
- self.stream = stream;
- self.enabled = true;
- // Wrap a MediaStreamSourceNode around the live input
- self.mediaStream = p5sound.audiocontext.createMediaStreamSource(stream);
- self.mediaStream.connect(self.output);
- if (successCallback)
- successCallback();
- // only send to the Amplitude reader, so we can see it but not hear it.
- self.amplitude.setInput(self.output);
- }, this._onStreamError = function (e) {
- if (errorCallback)
- errorCallback(e);
- else
- console.error(e);
- });
- } else {
- // if Firefox where users select their source via browser
- // if (typeof MediaStreamTrack.getSources === 'undefined') {
- // Only get the audio stream.
- window.navigator.getUserMedia({ 'audio': true }, this._onStream = function (stream) {
- self.stream = stream;
- self.enabled = true;
- // Wrap a MediaStreamSourceNode around the live input
- self.mediaStream = p5sound.audiocontext.createMediaStreamSource(stream);
- self.mediaStream.connect(self.output);
- // only send to the Amplitude reader, so we can see it but not hear it.
- self.amplitude.setInput(self.output);
- if (successCallback)
- successCallback();
- }, this._onStreamError = function (e) {
- if (errorCallback)
- errorCallback(e);
- else
- console.error(e);
- });
- }
- };
- /**
- * Turn the AudioIn off. If the AudioIn is stopped, it cannot getLevel().
- * If re-starting, the user may be prompted for permission access.
- *
- * @method stop
- */
- p5.AudioIn.prototype.stop = function () {
- if (this.stream) {
- // assume only one track
- this.stream.getTracks()[0].stop();
- }
- };
- /**
- * Connect to an audio unit. If no parameter is provided, will
- * connect to the master output (i.e. your speakers).<br/>
- *
- * @method connect
- * @param {Object} [unit] An object that accepts audio input,
- * such as an FFT
- */
- p5.AudioIn.prototype.connect = function (unit) {
- if (unit) {
- if (unit.hasOwnProperty('input')) {
- this.output.connect(unit.input);
- } else if (unit.hasOwnProperty('analyser')) {
- this.output.connect(unit.analyser);
- } else {
- this.output.connect(unit);
- }
- } else {
- this.output.connect(p5sound.input);
- }
- };
- /**
- * Disconnect the AudioIn from all audio units. For example, if
- * connect() had been called, disconnect() will stop sending
- * signal to your speakers.<br/>
- *
- * @method disconnect
- */
- p5.AudioIn.prototype.disconnect = function () {
- this.output.disconnect();
- // stay connected to amplitude even if not outputting to p5
- this.output.connect(this.amplitude.input);
- };
- /**
- * Read the Amplitude (volume level) of an AudioIn. The AudioIn
- * class contains its own instance of the Amplitude class to help
- * make it easy to get a microphone's volume level. Accepts an
- * optional smoothing value (0.0 < 1.0). <em>NOTE: AudioIn must
- * .start() before using .getLevel().</em><br/>
- *
- * @method getLevel
- * @param {Number} [smoothing] Smoothing is 0.0 by default.
- * Smooths values based on previous values.
- * @return {Number} Volume level (between 0.0 and 1.0)
- */
- p5.AudioIn.prototype.getLevel = function (smoothing) {
- if (smoothing) {
- this.amplitude.smoothing = smoothing;
- }
- return this.amplitude.getLevel();
- };
- /**
- * Add input sources to the list of available sources.
- *
- * @private
- */
- p5.AudioIn.prototype._gotSources = function (sourceInfos) {
- for (var i = 0; i < sourceInfos.length; i++) {
- var sourceInfo = sourceInfos[i];
- if (sourceInfo.kind === 'audio') {
- // add the inputs to inputSources
- //p5sound.inputSources.push(sourceInfo);
- return sourceInfo;
- }
- }
- };
- /**
- * Set amplitude (volume) of a mic input between 0 and 1.0. <br/>
- *
- * @method amp
- * @param {Number} vol between 0 and 1.0
- * @param {Number} [time] ramp time (optional)
- */
- p5.AudioIn.prototype.amp = function (vol, t) {
- if (t) {
- var rampTime = t || 0;
- var currentVol = this.output.gain.value;
- this.output.gain.cancelScheduledValues(p5sound.audiocontext.currentTime);
- this.output.gain.setValueAtTime(currentVol, p5sound.audiocontext.currentTime);
- this.output.gain.linearRampToValueAtTime(vol, rampTime + p5sound.audiocontext.currentTime);
- } else {
- this.output.gain.cancelScheduledValues(p5sound.audiocontext.currentTime);
- this.output.gain.setValueAtTime(vol, p5sound.audiocontext.currentTime);
- }
- };
- p5.AudioIn.prototype.listSources = function () {
- console.log('listSources is deprecated - please use AudioIn.getSources');
- console.log('input sources: ');
- if (p5sound.inputSources.length > 0) {
- return p5sound.inputSources;
- } else {
- return 'This browser does not support MediaStreamTrack.getSources()';
- }
- };
- /**
- * Chrome only. Returns a list of available input sources
- * and allows the user to set the media source. Firefox allows
- * the user to choose from input sources in the permissions dialogue
- * instead of enumerating available sources and selecting one.
- * Note: in order to have descriptive media names your page must be
- * served over a secure (HTTPS) connection and the page should
- * request user media before enumerating devices. Otherwise device
- * ID will be a long device ID number and does not specify device
- * type. For example see
- * https://simpl.info/getusermedia/sources/index.html vs.
- * http://simpl.info/getusermedia/sources/index.html
- *
- * @method getSources
- * @param {Function} callback a callback to handle the sources
- * when they have been enumerated
- * @example
- * <div><code>
- * var audiograb;
- *
- * function setup(){
- * //new audioIn
- * audioGrab = new p5.AudioIn();
- *
- * audioGrab.getSources(function(sourceList) {
- * //print out the array of available sources
- * console.log(sourceList);
- * //set the source to the first item in the inputSources array
- * audioGrab.setSource(0);
- * });
- * }
- * </code></div>
- */
- p5.AudioIn.prototype.getSources = function (callback) {
- if (typeof window.MediaStreamTrack.getSources === 'function') {
- window.MediaStreamTrack.getSources(function (data) {
- for (var i = 0, max = data.length; i < max; i++) {
- var sourceInfo = data[i];
- if (sourceInfo.kind === 'audio') {
- // add the inputs to inputSources
- p5sound.inputSources.push(sourceInfo);
- }
- }
- callback(p5sound.inputSources);
- });
- } else {
- console.log('This browser does not support MediaStreamTrack.getSources()');
- }
- };
- /**
- * Set the input source. Accepts a number representing a
- * position in the array returned by listSources().
- * This is only available in browsers that support
- * MediaStreamTrack.getSources(). Instead, some browsers
- * give users the option to set their own media source.<br/>
- *
- * @method setSource
- * @param {number} num position of input source in the array
- */
- p5.AudioIn.prototype.setSource = function (num) {
- // TO DO - set input by string or # (array position)
- var self = this;
- if (p5sound.inputSources.length > 0 && num < p5sound.inputSources.length) {
- // set the current source
- self.currentSource = num;
- console.log('set source to ' + p5sound.inputSources[self.currentSource].id);
- } else {
- console.log('unable to set input source');
- }
- };
- // private method
- p5.AudioIn.prototype.dispose = function () {
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- this.stop();
- if (this.output) {
- this.output.disconnect();
- }
- if (this.amplitude) {
- this.amplitude.disconnect();
- }
- this.amplitude = null;
- this.output = null;
- };
- }(master, errorHandler);
- var filter;
- filter = function () {
- 'use strict';
- var p5sound = master;
- /**
- * A p5.Filter uses a Web Audio Biquad Filter to filter
- * the frequency response of an input source. Inheriting
- * classes include:<br/>
- * * <code>p5.LowPass</code> - allows frequencies below
- * the cutoff frequency to pass through, and attenuates
- * frequencies above the cutoff.<br/>
- * * <code>p5.HighPass</code> - the opposite of a lowpass
- * filter. <br/>
- * * <code>p5.BandPass</code> - allows a range of
- * frequencies to pass through and attenuates the frequencies
- * below and above this frequency range.<br/>
- *
- * The <code>.res()</code> method controls either width of the
- * bandpass, or resonance of the low/highpass cutoff frequency.
- *
- * @class p5.Filter
- * @constructor
- * @param {String} [type] 'lowpass' (default), 'highpass', 'bandpass'
- * @return {Object} p5.Filter
- * @example
- * <div><code>
- * var fft, noise, filter;
- *
- * function setup() {
- * fill(255, 40, 255);
- *
- * filter = new p5.BandPass();
- *
- * noise = new p5.Noise();
- * // disconnect unfiltered noise,
- * // and connect to filter
- * noise.disconnect();
- * noise.connect(filter);
- * noise.start();
- *
- * fft = new p5.FFT();
- * }
- *
- * function draw() {
- * background(30);
- *
- * // set the BandPass frequency based on mouseX
- * var freq = map(mouseX, 0, width, 20, 10000);
- * filter.freq(freq);
- * // give the filter a narrow band (lower res = wider bandpass)
- * filter.res(50);
- *
- * // draw filtered spectrum
- * var spectrum = fft.analyze();
- * noStroke();
- * for (var i = 0; i < spectrum.length; i++) {
- * var x = map(i, 0, spectrum.length, 0, width);
- * var h = -height + map(spectrum[i], 0, 255, height, 0);
- * rect(x, height, width/spectrum.length, h);
- * }
- *
- * isMouseOverCanvas();
- * }
- *
- * function isMouseOverCanvas() {
- * var mX = mouseX, mY = mouseY;
- * if (mX > 0 && mX < width && mY < height && mY > 0) {
- * noise.amp(0.5, 0.2);
- * } else {
- * noise.amp(0, 0.2);
- * }
- * }
- * </code></div>
- */
- p5.Filter = function (type) {
- this.ac = p5sound.audiocontext;
- this.input = this.ac.createGain();
- this.output = this.ac.createGain();
- /**
- * The p5.Filter is built with a
- * <a href="http://www.w3.org/TR/webaudio/#BiquadFilterNode">
- * Web Audio BiquadFilter Node</a>.
- *
- * @property biquadFilter
- * @type {Object} Web Audio Delay Node
- */
- this.biquad = this.ac.createBiquadFilter();
- this.input.connect(this.biquad);
- this.biquad.connect(this.output);
- this.connect();
- if (type) {
- this.setType(type);
- }
- // add to the soundArray
- p5sound.soundArray.push(this);
- };
- /**
- * Filter an audio signal according to a set
- * of filter parameters.
- *
- * @method process
- * @param {Object} Signal An object that outputs audio
- * @param {Number} [freq] Frequency in Hz, from 10 to 22050
- * @param {Number} [res] Resonance/Width of the filter frequency
- * from 0.001 to 1000
- */
- p5.Filter.prototype.process = function (src, freq, res) {
- src.connect(this.input);
- this.set(freq, res);
- };
- /**
- * Set the frequency and the resonance of the filter.
- *
- * @method set
- * @param {Number} freq Frequency in Hz, from 10 to 22050
- * @param {Number} res Resonance (Q) from 0.001 to 1000
- * @param {Number} [timeFromNow] schedule this event to happen
- * seconds from now
- */
- p5.Filter.prototype.set = function (freq, res, time) {
- if (freq) {
- this.freq(freq, time);
- }
- if (res) {
- this.res(res, time);
- }
- };
- /**
- * Set the filter frequency, in Hz, from 10 to 22050 (the range of
- * human hearing, although in reality most people hear in a narrower
- * range).
- *
- * @method freq
- * @param {Number} freq Filter Frequency
- * @param {Number} [timeFromNow] schedule this event to happen
- * seconds from now
- * @return {Number} value Returns the current frequency value
- */
- p5.Filter.prototype.freq = function (freq, time) {
- var self = this;
- var t = time || 0;
- if (freq <= 0) {
- freq = 1;
- }
- if (typeof freq === 'number') {
- self.biquad.frequency.value = freq;
- self.biquad.frequency.cancelScheduledValues(this.ac.currentTime + 0.01 + t);
- self.biquad.frequency.exponentialRampToValueAtTime(freq, this.ac.currentTime + 0.02 + t);
- } else if (freq) {
- freq.connect(this.biquad.frequency);
- }
- return self.biquad.frequency.value;
- };
- /**
- * Controls either width of a bandpass frequency,
- * or the resonance of a low/highpass cutoff frequency.
- *
- * @method res
- * @param {Number} res Resonance/Width of filter freq
- * from 0.001 to 1000
- * @param {Number} [timeFromNow] schedule this event to happen
- * seconds from now
- * @return {Number} value Returns the current res value
- */
- p5.Filter.prototype.res = function (res, time) {
- var self = this;
- var t = time || 0;
- if (typeof res == 'number') {
- self.biquad.Q.value = res;
- self.biquad.Q.cancelScheduledValues(self.ac.currentTime + 0.01 + t);
- self.biquad.Q.linearRampToValueAtTime(res, self.ac.currentTime + 0.02 + t);
- } else if (res) {
- freq.connect(this.biquad.Q);
- }
- return self.biquad.Q.value;
- };
- /**
- * Set the type of a p5.Filter. Possible types include:
- * "lowpass" (default), "highpass", "bandpass",
- * "lowshelf", "highshelf", "peaking", "notch",
- * "allpass".
- *
- * @method setType
- * @param {String} t type of filter
- */
- p5.Filter.prototype.setType = function (t) {
- this.biquad.type = t;
- };
- /**
- * Set the output level of the filter.
- *
- * @method amp
- * @param {Number} volume amplitude between 0 and 1.0
- * @param {Number} [rampTime] create a fade that lasts rampTime
- * @param {Number} [timeFromNow] schedule this event to happen
- * seconds from now
- */
- p5.Filter.prototype.amp = function (vol, rampTime, tFromNow) {
- var rampTime = rampTime || 0;
- var tFromNow = tFromNow || 0;
- var now = p5sound.audiocontext.currentTime;
- var currentVol = this.output.gain.value;
- this.output.gain.cancelScheduledValues(now);
- this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001);
- this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001);
- };
- /**
- * Send output to a p5.sound or web audio object
- *
- * @method connect
- * @param {Object} unit
- */
- p5.Filter.prototype.connect = function (unit) {
- var u = unit || p5.soundOut.input;
- this.output.connect(u);
- };
- /**
- * Disconnect all output.
- *
- * @method disconnect
- */
- p5.Filter.prototype.disconnect = function () {
- this.output.disconnect();
- };
- p5.Filter.prototype.dispose = function () {
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- this.input.disconnect();
- this.input = undefined;
- this.output.disconnect();
- this.output = undefined;
- this.biquad.disconnect();
- this.biquad = undefined;
- };
- /**
- * Constructor: <code>new p5.LowPass()</code> Filter.
- * This is the same as creating a p5.Filter and then calling
- * its method <code>setType('lowpass')</code>.
- * See p5.Filter for methods.
- *
- * @method p5.LowPass
- */
- p5.LowPass = function () {
- p5.Filter.call(this, 'lowpass');
- };
- p5.LowPass.prototype = Object.create(p5.Filter.prototype);
- /**
- * Constructor: <code>new p5.HighPass()</code> Filter.
- * This is the same as creating a p5.Filter and then calling
- * its method <code>setType('highpass')</code>.
- * See p5.Filter for methods.
- *
- * @method p5.HighPass
- */
- p5.HighPass = function () {
- p5.Filter.call(this, 'highpass');
- };
- p5.HighPass.prototype = Object.create(p5.Filter.prototype);
- /**
- * Constructor: <code>new p5.BandPass()</code> Filter.
- * This is the same as creating a p5.Filter and then calling
- * its method <code>setType('bandpass')</code>.
- * See p5.Filter for methods.
- *
- * @method p5.BandPass
- */
- p5.BandPass = function () {
- p5.Filter.call(this, 'bandpass');
- };
- p5.BandPass.prototype = Object.create(p5.Filter.prototype);
- }(master);
- var delay;
- delay = function () {
- 'use strict';
- var p5sound = master;
- var Filter = filter;
- /**
- * Delay is an echo effect. It processes an existing sound source,
- * and outputs a delayed version of that sound. The p5.Delay can
- * produce different effects depending on the delayTime, feedback,
- * filter, and type. In the example below, a feedback of 0.5 will
- * produce a looping delay that decreases in volume by
- * 50% each repeat. A filter will cut out the high frequencies so
- * that the delay does not sound as piercing as the original source.
- *
- * @class p5.Delay
- * @constructor
- * @return {Object} Returns a p5.Delay object
- * @example
- * <div><code>
- * var noise, env, delay;
- *
- * function setup() {
- * background(0);
- * noStroke();
- * fill(255);
- * textAlign(CENTER);
- * text('click to play', width/2, height/2);
- *
- * noise = new p5.Noise('brown');
- * noise.amp(0);
- * noise.start();
- *
- * delay = new p5.Delay();
- *
- * // delay.process() accepts 4 parameters:
- * // source, delayTime, feedback, filter frequency
- * // play with these numbers!!
- * delay.process(noise, .12, .7, 2300);
- *
- * // play the noise with an envelope,
- * // a series of fades ( time / value pairs )
- * env = new p5.Env(.01, 0.2, .2, .1);
- * }
- *
- * // mouseClick triggers envelope
- * function mouseClicked() {
- * // is mouse over canvas?
- * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
- * env.play(noise);
- * }
- * }
- * </code></div>
- */
- p5.Delay = function () {
- this.ac = p5sound.audiocontext;
- this.input = this.ac.createGain();
- this.output = this.ac.createGain();
- this._split = this.ac.createChannelSplitter(2);
- this._merge = this.ac.createChannelMerger(2);
- this._leftGain = this.ac.createGain();
- this._rightGain = this.ac.createGain();
- /**
- * The p5.Delay is built with two
- * <a href="http://www.w3.org/TR/webaudio/#DelayNode">
- * Web Audio Delay Nodes</a>, one for each stereo channel.
- *
- * @property leftDelay
- * @type {Object} Web Audio Delay Node
- */
- this.leftDelay = this.ac.createDelay();
- /**
- * The p5.Delay is built with two
- * <a href="http://www.w3.org/TR/webaudio/#DelayNode">
- * Web Audio Delay Nodes</a>, one for each stereo channel.
- *
- * @property rightDelay
- * @type {Object} Web Audio Delay Node
- */
- this.rightDelay = this.ac.createDelay();
- this._leftFilter = new p5.Filter();
- this._rightFilter = new p5.Filter();
- this._leftFilter.disconnect();
- this._rightFilter.disconnect();
- this._leftFilter.biquad.frequency.setValueAtTime(1200, this.ac.currentTime);
- this._rightFilter.biquad.frequency.setValueAtTime(1200, this.ac.currentTime);
- this._leftFilter.biquad.Q.setValueAtTime(0.3, this.ac.currentTime);
- this._rightFilter.biquad.Q.setValueAtTime(0.3, this.ac.currentTime);
- // graph routing
- this.input.connect(this._split);
- this.leftDelay.connect(this._leftGain);
- this.rightDelay.connect(this._rightGain);
- this._leftGain.connect(this._leftFilter.input);
- this._rightGain.connect(this._rightFilter.input);
- this._merge.connect(this.output);
- this.output.connect(p5.soundOut.input);
- this._leftFilter.biquad.gain.setValueAtTime(1, this.ac.currentTime);
- this._rightFilter.biquad.gain.setValueAtTime(1, this.ac.currentTime);
- // default routing
- this.setType(0);
- this._maxDelay = this.leftDelay.delayTime.maxValue;
- // add this p5.SoundFile to the soundArray
- p5sound.soundArray.push(this);
- };
- /**
- * Add delay to an audio signal according to a set
- * of delay parameters.
- *
- * @method process
- * @param {Object} Signal An object that outputs audio
- * @param {Number} [delayTime] Time (in seconds) of the delay/echo.
- * Some browsers limit delayTime to
- * 1 second.
- * @param {Number} [feedback] sends the delay back through itself
- * in a loop that decreases in volume
- * each time.
- * @param {Number} [lowPass] Cutoff frequency. Only frequencies
- * below the lowPass will be part of the
- * delay.
- */
- p5.Delay.prototype.process = function (src, _delayTime, _feedback, _filter) {
- var feedback = _feedback || 0;
- var delayTime = _delayTime || 0;
- if (feedback >= 1) {
- throw new Error('Feedback value will force a positive feedback loop.');
- }
- if (delayTime >= this._maxDelay) {
- throw new Error('Delay Time exceeds maximum delay time of ' + this._maxDelay + ' second.');
- }
- src.connect(this.input);
- this.leftDelay.delayTime.setValueAtTime(delayTime, this.ac.currentTime);
- this.rightDelay.delayTime.setValueAtTime(delayTime, this.ac.currentTime);
- this._leftGain.gain.setValueAtTime(feedback, this.ac.currentTime);
- this._rightGain.gain.setValueAtTime(feedback, this.ac.currentTime);
- if (_filter) {
- this._leftFilter.freq(_filter);
- this._rightFilter.freq(_filter);
- }
- };
- /**
- * Set the delay (echo) time, in seconds. Usually this value will be
- * a floating point number between 0.0 and 1.0.
- *
- * @method delayTime
- * @param {Number} delayTime Time (in seconds) of the delay
- */
- p5.Delay.prototype.delayTime = function (t) {
- // if t is an audio node...
- if (typeof t !== 'number') {
- t.connect(this.leftDelay.delayTime);
- t.connect(this.rightDelay.delayTime);
- } else {
- this.leftDelay.delayTime.cancelScheduledValues(this.ac.currentTime);
- this.rightDelay.delayTime.cancelScheduledValues(this.ac.currentTime);
- this.leftDelay.delayTime.linearRampToValueAtTime(t, this.ac.currentTime);
- this.rightDelay.delayTime.linearRampToValueAtTime(t, this.ac.currentTime);
- }
- };
- /**
- * Feedback occurs when Delay sends its signal back through its input
- * in a loop. The feedback amount determines how much signal to send each
- * time through the loop. A feedback greater than 1.0 is not desirable because
- * it will increase the overall output each time through the loop,
- * creating an infinite feedback loop.
- *
- * @method feedback
- * @param {Number|Object} feedback 0.0 to 1.0, or an object such as an
- * Oscillator that can be used to
- * modulate this param
- */
- p5.Delay.prototype.feedback = function (f) {
- // if f is an audio node...
- if (typeof f !== 'number') {
- f.connect(this._leftGain.gain);
- f.connect(this._rightGain.gain);
- } else if (f >= 1) {
- throw new Error('Feedback value will force a positive feedback loop.');
- } else {
- this._leftGain.gain.exponentialRampToValueAtTime(f, this.ac.currentTime);
- this._rightGain.gain.exponentialRampToValueAtTime(f, this.ac.currentTime);
- }
- };
- /**
- * Set a lowpass filter frequency for the delay. A lowpass filter
- * will cut off any frequencies higher than the filter frequency.
- *
- * @method filter
- * @param {Number|Object} cutoffFreq A lowpass filter will cut off any
- * frequencies higher than the filter frequency.
- * @param {Number|Object} res Resonance of the filter frequency
- * cutoff, or an object (i.e. a p5.Oscillator)
- * that can be used to modulate this parameter.
- * High numbers (i.e. 15) will produce a resonance,
- * low numbers (i.e. .2) will produce a slope.
- */
- p5.Delay.prototype.filter = function (freq, q) {
- this._leftFilter.set(freq, q);
- this._rightFilter.set(freq, q);
- };
- /**
- * Choose a preset type of delay. 'pingPong' bounces the signal
- * from the left to the right channel to produce a stereo effect.
- * Any other parameter will revert to the default delay setting.
- *
- * @method setType
- * @param {String|Number} type 'pingPong' (1) or 'default' (0)
- */
- p5.Delay.prototype.setType = function (t) {
- if (t === 1) {
- t = 'pingPong';
- }
- this._split.disconnect();
- this._leftFilter.disconnect();
- this._rightFilter.disconnect();
- this._split.connect(this.leftDelay, 0);
- this._split.connect(this.rightDelay, 1);
- switch (t) {
- case 'pingPong':
- this._rightFilter.setType(this._leftFilter.biquad.type);
- this._leftFilter.output.connect(this._merge, 0, 0);
- this._rightFilter.output.connect(this._merge, 0, 1);
- this._leftFilter.output.connect(this.rightDelay);
- this._rightFilter.output.connect(this.leftDelay);
- break;
- default:
- this._leftFilter.output.connect(this._merge, 0, 0);
- this._leftFilter.output.connect(this._merge, 0, 1);
- this._leftFilter.output.connect(this.leftDelay);
- this._leftFilter.output.connect(this.rightDelay);
- }
- };
- /**
- * Set the output level of the delay effect.
- *
- * @method amp
- * @param {Number} volume amplitude between 0 and 1.0
- * @param {Number} [rampTime] create a fade that lasts rampTime
- * @param {Number} [timeFromNow] schedule this event to happen
- * seconds from now
- */
- p5.Delay.prototype.amp = function (vol, rampTime, tFromNow) {
- var rampTime = rampTime || 0;
- var tFromNow = tFromNow || 0;
- var now = p5sound.audiocontext.currentTime;
- var currentVol = this.output.gain.value;
- this.output.gain.cancelScheduledValues(now);
- this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001);
- this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001);
- };
- /**
- * Send output to a p5.sound or web audio object
- *
- * @method connect
- * @param {Object} unit
- */
- p5.Delay.prototype.connect = function (unit) {
- var u = unit || p5.soundOut.input;
- this.output.connect(u);
- };
- /**
- * Disconnect all output.
- *
- * @method disconnect
- */
- p5.Delay.prototype.disconnect = function () {
- this.output.disconnect();
- };
- p5.Delay.prototype.dispose = function () {
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- this.input.disconnect();
- this.output.disconnect();
- this._split.disconnect();
- this._leftFilter.disconnect();
- this._rightFilter.disconnect();
- this._merge.disconnect();
- this._leftGain.disconnect();
- this._rightGain.disconnect();
- this.leftDelay.disconnect();
- this.rightDelay.disconnect();
- this.input = undefined;
- this.output = undefined;
- this._split = undefined;
- this._leftFilter = undefined;
- this._rightFilter = undefined;
- this._merge = undefined;
- this._leftGain = undefined;
- this._rightGain = undefined;
- this.leftDelay = undefined;
- this.rightDelay = undefined;
- };
- }(master, filter);
- var reverb;
- reverb = function () {
- 'use strict';
- var p5sound = master;
- var CustomError = errorHandler;
- /**
- * Reverb adds depth to a sound through a large number of decaying
- * echoes. It creates the perception that sound is occurring in a
- * physical space. The p5.Reverb has paramters for Time (how long does the
- * reverb last) and decayRate (how much the sound decays with each echo)
- * that can be set with the .set() or .process() methods. The p5.Convolver
- * extends p5.Reverb allowing you to recreate the sound of actual physical
- * spaces through convolution.
- *
- * @class p5.Reverb
- * @constructor
- * @example
- * <div><code>
- * var soundFile, reverb;
- * function preload() {
- * soundFile = loadSound('assets/Damscray_DancingTiger.mp3');
- * }
- *
- * function setup() {
- * reverb = new p5.Reverb();
- * soundFile.disconnect(); // so we'll only hear reverb...
- *
- * // connect soundFile to reverb, process w/
- * // 3 second reverbTime, decayRate of 2%
- * reverb.process(soundFile, 3, 2);
- * soundFile.play();
- * }
- * </code></div>
- */
- p5.Reverb = function () {
- this.ac = p5sound.audiocontext;
- this.convolverNode = this.ac.createConvolver();
- this.input = this.ac.createGain();
- this.output = this.ac.createGain();
- // otherwise, Safari distorts
- this.input.gain.value = 0.5;
- this.input.connect(this.convolverNode);
- this.convolverNode.connect(this.output);
- // default params
- this._seconds = 3;
- this._decay = 2;
- this._reverse = false;
- this._buildImpulse();
- this.connect();
- p5sound.soundArray.push(this);
- };
- /**
- * Connect a source to the reverb, and assign reverb parameters.
- *
- * @method process
- * @param {Object} src p5.sound / Web Audio object with a sound
- * output.
- * @param {Number} [seconds] Duration of the reverb, in seconds.
- * Min: 0, Max: 10. Defaults to 3.
- * @param {Number} [decayRate] Percentage of decay with each echo.
- * Min: 0, Max: 100. Defaults to 2.
- * @param {Boolean} [reverse] Play the reverb backwards or forwards.
- */
- p5.Reverb.prototype.process = function (src, seconds, decayRate, reverse) {
- src.connect(this.input);
- var rebuild = false;
- if (seconds) {
- this._seconds = seconds;
- rebuild = true;
- }
- if (decayRate) {
- this._decay = decayRate;
- }
- if (reverse) {
- this._reverse = reverse;
- }
- if (rebuild) {
- this._buildImpulse();
- }
- };
- /**
- * Set the reverb settings. Similar to .process(), but without
- * assigning a new input.
- *
- * @method set
- * @param {Number} [seconds] Duration of the reverb, in seconds.
- * Min: 0, Max: 10. Defaults to 3.
- * @param {Number} [decayRate] Percentage of decay with each echo.
- * Min: 0, Max: 100. Defaults to 2.
- * @param {Boolean} [reverse] Play the reverb backwards or forwards.
- */
- p5.Reverb.prototype.set = function (seconds, decayRate, reverse) {
- var rebuild = false;
- if (seconds) {
- this._seconds = seconds;
- rebuild = true;
- }
- if (decayRate) {
- this._decay = decayRate;
- }
- if (reverse) {
- this._reverse = reverse;
- }
- if (rebuild) {
- this._buildImpulse();
- }
- };
- /**
- * Set the output level of the delay effect.
- *
- * @method amp
- * @param {Number} volume amplitude between 0 and 1.0
- * @param {Number} [rampTime] create a fade that lasts rampTime
- * @param {Number} [timeFromNow] schedule this event to happen
- * seconds from now
- */
- p5.Reverb.prototype.amp = function (vol, rampTime, tFromNow) {
- var rampTime = rampTime || 0;
- var tFromNow = tFromNow || 0;
- var now = p5sound.audiocontext.currentTime;
- var currentVol = this.output.gain.value;
- this.output.gain.cancelScheduledValues(now);
- this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001);
- this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001);
- };
- /**
- * Send output to a p5.sound or web audio object
- *
- * @method connect
- * @param {Object} unit
- */
- p5.Reverb.prototype.connect = function (unit) {
- var u = unit || p5.soundOut.input;
- this.output.connect(u.input ? u.input : u);
- };
- /**
- * Disconnect all output.
- *
- * @method disconnect
- */
- p5.Reverb.prototype.disconnect = function () {
- this.output.disconnect();
- };
- /**
- * Inspired by Simple Reverb by Jordan Santell
- * https://github.com/web-audio-components/simple-reverb/blob/master/index.js
- *
- * Utility function for building an impulse response
- * based on the module parameters.
- *
- * @private
- */
- p5.Reverb.prototype._buildImpulse = function () {
- var rate = this.ac.sampleRate;
- var length = rate * this._seconds;
- var decay = this._decay;
- var impulse = this.ac.createBuffer(2, length, rate);
- var impulseL = impulse.getChannelData(0);
- var impulseR = impulse.getChannelData(1);
- var n, i;
- for (i = 0; i < length; i++) {
- n = this.reverse ? length - i : i;
- impulseL[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay);
- impulseR[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay);
- }
- this.convolverNode.buffer = impulse;
- };
- p5.Reverb.prototype.dispose = function () {
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- if (this.convolverNode) {
- this.convolverNode.buffer = null;
- this.convolverNode = null;
- }
- if (typeof this.output !== 'undefined') {
- this.output.disconnect();
- this.output = null;
- }
- if (typeof this.panner !== 'undefined') {
- this.panner.disconnect();
- this.panner = null;
- }
- };
- // =======================================================================
- // *** p5.Convolver ***
- // =======================================================================
- /**
- * <p>p5.Convolver extends p5.Reverb. It can emulate the sound of real
- * physical spaces through a process called <a href="
- * https://en.wikipedia.org/wiki/Convolution_reverb#Real_space_simulation">
- * convolution</a>.</p>
- *
- * <p>Convolution multiplies any audio input by an "impulse response"
- * to simulate the dispersion of sound over time. The impulse response is
- * generated from an audio file that you provide. One way to
- * generate an impulse response is to pop a balloon in a reverberant space
- * and record the echo. Convolution can also be used to experiment with
- * sound.</p>
- *
- * <p>Use the method <code>createConvolution(path)</code> to instantiate a
- * p5.Convolver with a path to your impulse response audio file.</p>
- *
- * @class p5.Convolver
- * @constructor
- * @param {String} path path to a sound file
- * @param {Function} [callback] function to call when loading succeeds
- * @param {Function} [errorCallback] function to call if loading fails.
- * This function will receive an error or
- * XMLHttpRequest object with information
- * about what went wrong.
- * @example
- * <div><code>
- * var cVerb, sound;
- * function preload() {
- * // We have both MP3 and OGG versions of all sound assets
- * soundFormats('ogg', 'mp3');
- *
- * // Try replacing 'bx-spring' with other soundfiles like
- * // 'concrete-tunnel' 'small-plate' 'drum' 'beatbox'
- * cVerb = createConvolver('assets/bx-spring.mp3');
- *
- * // Try replacing 'Damscray_DancingTiger' with
- * // 'beat', 'doorbell', lucky_dragons_-_power_melody'
- * sound = loadSound('assets/Damscray_DancingTiger.mp3');
- * }
- *
- * function setup() {
- * // disconnect from master output...
- * sound.disconnect();
- *
- * // ...and process with cVerb
- * // so that we only hear the convolution
- * cVerb.process(sound);
- *
- * sound.play();
- * }
- * </code></div>
- */
- p5.Convolver = function (path, callback, errorCallback) {
- this.ac = p5sound.audiocontext;
- /**
- * Internally, the p5.Convolver uses the a
- * <a href="http://www.w3.org/TR/webaudio/#ConvolverNode">
- * Web Audio Convolver Node</a>.
- *
- * @property convolverNode
- * @type {Object} Web Audio Convolver Node
- */
- this.convolverNode = this.ac.createConvolver();
- this.input = this.ac.createGain();
- this.output = this.ac.createGain();
- // otherwise, Safari distorts
- this.input.gain.value = 0.5;
- this.input.connect(this.convolverNode);
- this.convolverNode.connect(this.output);
- if (path) {
- this.impulses = [];
- this._loadBuffer(path, callback, errorCallback);
- } else {
- // parameters
- this._seconds = 3;
- this._decay = 2;
- this._reverse = false;
- this._buildImpulse();
- }
- this.connect();
- p5sound.soundArray.push(this);
- };
- p5.Convolver.prototype = Object.create(p5.Reverb.prototype);
- p5.prototype.registerPreloadMethod('createConvolver', p5.prototype);
- /**
- * Create a p5.Convolver. Accepts a path to a soundfile
- * that will be used to generate an impulse response.
- *
- * @method createConvolver
- * @param {String} path path to a sound file
- * @param {Function} [callback] function to call if loading is successful.
- * The object will be passed in as the argument
- * to the callback function.
- * @param {Function} [errorCallback] function to call if loading is not successful.
- * A custom error will be passed in as the argument
- * to the callback function.
- * @return {p5.Convolver}
- * @example
- * <div><code>
- * var cVerb, sound;
- * function preload() {
- * // We have both MP3 and OGG versions of all sound assets
- * soundFormats('ogg', 'mp3');
- *
- * // Try replacing 'bx-spring' with other soundfiles like
- * // 'concrete-tunnel' 'small-plate' 'drum' 'beatbox'
- * cVerb = createConvolver('assets/bx-spring.mp3');
- *
- * // Try replacing 'Damscray_DancingTiger' with
- * // 'beat', 'doorbell', lucky_dragons_-_power_melody'
- * sound = loadSound('assets/Damscray_DancingTiger.mp3');
- * }
- *
- * function setup() {
- * // disconnect from master output...
- * sound.disconnect();
- *
- * // ...and process with cVerb
- * // so that we only hear the convolution
- * cVerb.process(sound);
- *
- * sound.play();
- * }
- * </code></div>
- */
- p5.prototype.createConvolver = function (path, callback, errorCallback) {
- // if loading locally without a server
- if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') {
- alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS');
- }
- var cReverb = new p5.Convolver(path, callback, errorCallback);
- cReverb.impulses = [];
- return cReverb;
- };
- /**
- * Private method to load a buffer as an Impulse Response,
- * assign it to the convolverNode, and add to the Array of .impulses.
- *
- * @param {String} path
- * @param {Function} callback
- * @param {Function} errorCallback
- * @private
- */
- p5.Convolver.prototype._loadBuffer = function (path, callback, errorCallback) {
- var path = p5.prototype._checkFileFormats(path);
- var self = this;
- var errorTrace = new Error().stack;
- var ac = p5.prototype.getAudioContext();
- var request = new XMLHttpRequest();
- request.open('GET', path, true);
- request.responseType = 'arraybuffer';
- request.onload = function () {
- if (request.status == 200) {
- // on success loading file:
- ac.decodeAudioData(request.response, function (buff) {
- var buffer = {};
- var chunks = path.split('/');
- buffer.name = chunks[chunks.length - 1];
- buffer.audioBuffer = buff;
- self.impulses.push(buffer);
- self.convolverNode.buffer = buffer.audioBuffer;
- if (callback) {
- callback(buffer);
- }
- }, // error decoding buffer. "e" is undefined in Chrome 11/22/2015
- function (e) {
- var err = new CustomError('decodeAudioData', errorTrace, self.url);
- var msg = 'AudioContext error at decodeAudioData for ' + self.url;
- if (errorCallback) {
- err.msg = msg;
- errorCallback(err);
- } else {
- console.error(msg + '\n The error stack trace includes: \n' + err.stack);
- }
- });
- } else {
- var err = new CustomError('loadConvolver', errorTrace, self.url);
- var msg = 'Unable to load ' + self.url + '. The request status was: ' + request.status + ' (' + request.statusText + ')';
- if (errorCallback) {
- err.message = msg;
- errorCallback(err);
- } else {
- console.error(msg + '\n The error stack trace includes: \n' + err.stack);
- }
- }
- };
- // if there is another error, aside from 404...
- request.onerror = function (e) {
- var err = new CustomError('loadConvolver', errorTrace, self.url);
- var msg = 'There was no response from the server at ' + self.url + '. Check the url and internet connectivity.';
- if (errorCallback) {
- err.message = msg;
- errorCallback(err);
- } else {
- console.error(msg + '\n The error stack trace includes: \n' + err.stack);
- }
- };
- request.send();
- };
- p5.Convolver.prototype.set = null;
- /**
- * Connect a source to the reverb, and assign reverb parameters.
- *
- * @method process
- * @param {Object} src p5.sound / Web Audio object with a sound
- * output.
- * @example
- * <div><code>
- * var cVerb, sound;
- * function preload() {
- * soundFormats('ogg', 'mp3');
- *
- * cVerb = createConvolver('assets/concrete-tunnel.mp3');
- *
- * sound = loadSound('assets/beat.mp3');
- * }
- *
- * function setup() {
- * // disconnect from master output...
- * sound.disconnect();
- *
- * // ...and process with (i.e. connect to) cVerb
- * // so that we only hear the convolution
- * cVerb.process(sound);
- *
- * sound.play();
- * }
- * </code></div>
- */
- p5.Convolver.prototype.process = function (src) {
- src.connect(this.input);
- };
- /**
- * If you load multiple impulse files using the .addImpulse method,
- * they will be stored as Objects in this Array. Toggle between them
- * with the <code>toggleImpulse(id)</code> method.
- *
- * @property impulses
- * @type {Array} Array of Web Audio Buffers
- */
- p5.Convolver.prototype.impulses = [];
- /**
- * Load and assign a new Impulse Response to the p5.Convolver.
- * The impulse is added to the <code>.impulses</code> array. Previous
- * impulses can be accessed with the <code>.toggleImpulse(id)</code>
- * method.
- *
- * @method addImpulse
- * @param {String} path path to a sound file
- * @param {Function} callback function (optional)
- * @param {Function} errorCallback function (optional)
- */
- p5.Convolver.prototype.addImpulse = function (path, callback, errorCallback) {
- // if loading locally without a server
- if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') {
- alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS');
- }
- this._loadBuffer(path, callback, errorCallback);
- };
- /**
- * Similar to .addImpulse, except that the <code>.impulses</code>
- * Array is reset to save memory. A new <code>.impulses</code>
- * array is created with this impulse as the only item.
- *
- * @method resetImpulse
- * @param {String} path path to a sound file
- * @param {Function} callback function (optional)
- * @param {Function} errorCallback function (optional)
- */
- p5.Convolver.prototype.resetImpulse = function (path, callback, errorCallback) {
- // if loading locally without a server
- if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') {
- alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS');
- }
- this.impulses = [];
- this._loadBuffer(path, callback, errorCallback);
- };
- /**
- * If you have used <code>.addImpulse()</code> to add multiple impulses
- * to a p5.Convolver, then you can use this method to toggle between
- * the items in the <code>.impulses</code> Array. Accepts a parameter
- * to identify which impulse you wish to use, identified either by its
- * original filename (String) or by its position in the <code>.impulses
- * </code> Array (Number).<br/>
- * You can access the objects in the .impulses Array directly. Each
- * Object has two attributes: an <code>.audioBuffer</code> (type:
- * Web Audio <a href="
- * http://webaudio.github.io/web-audio-api/#the-audiobuffer-interface">
- * AudioBuffer)</a> and a <code>.name</code>, a String that corresponds
- * with the original filename.
- *
- * @method toggleImpulse
- * @param {String|Number} id Identify the impulse by its original filename
- * (String), or by its position in the
- * <code>.impulses</code> Array (Number).
- */
- p5.Convolver.prototype.toggleImpulse = function (id) {
- if (typeof id === 'number' && id < this.impulses.length) {
- this.convolverNode.buffer = this.impulses[id].audioBuffer;
- }
- if (typeof id === 'string') {
- for (var i = 0; i < this.impulses.length; i++) {
- if (this.impulses[i].name === id) {
- this.convolverNode.buffer = this.impulses[i].audioBuffer;
- break;
- }
- }
- }
- };
- p5.Convolver.prototype.dispose = function () {
- // remove all the Impulse Response buffers
- for (var i in this.impulses) {
- this.impulses[i] = null;
- }
- this.convolverNode.disconnect();
- this.concolverNode = null;
- if (typeof this.output !== 'undefined') {
- this.output.disconnect();
- this.output = null;
- }
- if (typeof this.panner !== 'undefined') {
- this.panner.disconnect();
- this.panner = null;
- }
- };
- }(master, errorHandler, sndcore);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_core_TimelineState;
- Tone_core_TimelineState = function (Tone) {
- 'use strict';
- Tone.TimelineState = function (initial) {
- Tone.Timeline.call(this);
- this._initial = initial;
- };
- Tone.extend(Tone.TimelineState, Tone.Timeline);
- Tone.TimelineState.prototype.getStateAtTime = function (time) {
- var event = this.getEvent(time);
- if (event !== null) {
- return event.state;
- } else {
- return this._initial;
- }
- };
- Tone.TimelineState.prototype.setStateAtTime = function (state, time) {
- this.addEvent({
- 'state': state,
- 'time': this.toSeconds(time)
- });
- };
- return Tone.TimelineState;
- }(Tone_core_Tone, Tone_core_Timeline);
- /** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/
- var Tone_core_Clock;
- Tone_core_Clock = function (Tone) {
- 'use strict';
- Tone.Clock = function () {
- var options = this.optionsObject(arguments, [
- 'callback',
- 'frequency'
- ], Tone.Clock.defaults);
- this.callback = options.callback;
- this._lookAhead = 'auto';
- this._computedLookAhead = 1 / 60;
- this._threshold = 0.5;
- this._nextTick = -1;
- this._lastUpdate = 0;
- this._loopID = -1;
- this.frequency = new Tone.TimelineSignal(options.frequency, Tone.Type.Frequency);
- this.ticks = 0;
- this._state = new Tone.TimelineState(Tone.State.Stopped);
- this._boundLoop = this._loop.bind(this);
- this._readOnly('frequency');
- this._loop();
- };
- Tone.extend(Tone.Clock);
- Tone.Clock.defaults = {
- 'callback': Tone.noOp,
- 'frequency': 1,
- 'lookAhead': 'auto'
- };
- Object.defineProperty(Tone.Clock.prototype, 'state', {
- get: function () {
- return this._state.getStateAtTime(this.now());
- }
- });
- Object.defineProperty(Tone.Clock.prototype, 'lookAhead', {
- get: function () {
- return this._lookAhead;
- },
- set: function (val) {
- if (val === 'auto') {
- this._lookAhead = 'auto';
- } else {
- this._lookAhead = this.toSeconds(val);
- }
- }
- });
- Tone.Clock.prototype.start = function (time, offset) {
- time = this.toSeconds(time);
- if (this._state.getStateAtTime(time) !== Tone.State.Started) {
- this._state.addEvent({
- 'state': Tone.State.Started,
- 'time': time,
- 'offset': offset
- });
- }
- return this;
- };
- Tone.Clock.prototype.stop = function (time) {
- time = this.toSeconds(time);
- if (this._state.getStateAtTime(time) !== Tone.State.Stopped) {
- this._state.setStateAtTime(Tone.State.Stopped, time);
- }
- return this;
- };
- Tone.Clock.prototype.pause = function (time) {
- time = this.toSeconds(time);
- if (this._state.getStateAtTime(time) === Tone.State.Started) {
- this._state.setStateAtTime(Tone.State.Paused, time);
- }
- return this;
- };
- Tone.Clock.prototype._loop = function (time) {
- this._loopID = requestAnimationFrame(this._boundLoop);
- if (this._lookAhead === 'auto') {
- if (!this.isUndef(time)) {
- var diff = (time - this._lastUpdate) / 1000;
- this._lastUpdate = time;
- if (diff < this._threshold) {
- this._computedLookAhead = (9 * this._computedLookAhead + diff) / 10;
- }
- }
- } else {
- this._computedLookAhead = this._lookAhead;
- }
- var now = this.now();
- var lookAhead = this._computedLookAhead * 2;
- var event = this._state.getEvent(now + lookAhead);
- var state = Tone.State.Stopped;
- if (event) {
- state = event.state;
- if (this._nextTick === -1 && state === Tone.State.Started) {
- this._nextTick = event.time;
- if (!this.isUndef(event.offset)) {
- this.ticks = event.offset;
- }
- }
- }
- if (state === Tone.State.Started) {
- while (now + lookAhead > this._nextTick) {
- if (now > this._nextTick + this._threshold) {
- this._nextTick = now;
- }
- var tickTime = this._nextTick;
- this._nextTick += 1 / this.frequency.getValueAtTime(this._nextTick);
- this.callback(tickTime);
- this.ticks++;
- }
- } else if (state === Tone.State.Stopped) {
- this._nextTick = -1;
- this.ticks = 0;
- }
- };
- Tone.Clock.prototype.getStateAtTime = function (time) {
- return this._state.getStateAtTime(time);
- };
- Tone.Clock.prototype.dispose = function () {
- cancelAnimationFrame(this._loopID);
- Tone.TimelineState.prototype.dispose.call(this);
- this._writable('frequency');
- this.frequency.dispose();
- this.frequency = null;
- this._boundLoop = Tone.noOp;
- this._nextTick = Infinity;
- this.callback = null;
- this._state.dispose();
- this._state = null;
- };
- return Tone.Clock;
- }(Tone_core_Tone, Tone_signal_TimelineSignal);
- var metro;
- metro = function () {
- 'use strict';
- var p5sound = master;
- // requires the Tone.js library's Clock (MIT license, Yotam Mann)
- // https://github.com/TONEnoTONE/Tone.js/
- var Clock = Tone_core_Clock;
- var ac = p5sound.audiocontext;
- // var upTick = false;
- p5.Metro = function () {
- this.clock = new Clock({ 'callback': this.ontick.bind(this) });
- this.syncedParts = [];
- this.bpm = 120;
- // gets overridden by p5.Part
- this._init();
- this.tickCallback = function () {
- };
- };
- var prevTick = 0;
- var tatumTime = 0;
- p5.Metro.prototype.ontick = function (tickTime) {
- var elapsedTime = tickTime - prevTick;
- var secondsFromNow = tickTime - p5sound.audiocontext.currentTime;
- if (elapsedTime - tatumTime <= -0.02) {
- return;
- } else {
- prevTick = tickTime;
- // for all of the active things on the metro:
- for (var i in this.syncedParts) {
- var thisPart = this.syncedParts[i];
- if (!thisPart.isPlaying)
- return;
- thisPart.incrementStep(secondsFromNow);
- // each synced source keeps track of its own beat number
- for (var j in thisPart.phrases) {
- var thisPhrase = thisPart.phrases[j];
- var phraseArray = thisPhrase.sequence;
- var bNum = this.metroTicks % phraseArray.length;
- if (phraseArray[bNum] !== 0 && (this.metroTicks < phraseArray.length || !thisPhrase.looping)) {
- thisPhrase.callback(secondsFromNow, phraseArray[bNum]);
- }
- }
- }
- this.metroTicks += 1;
- this.tickCallback(secondsFromNow);
- }
- };
- p5.Metro.prototype.setBPM = function (bpm, rampTime) {
- var beatTime = 60 / (bpm * this.tatums);
- var now = p5sound.audiocontext.currentTime;
- tatumTime = beatTime;
- var rampTime = rampTime || 0;
- this.clock.frequency.setValueAtTime(this.clock.frequency.value, now);
- this.clock.frequency.linearRampToValueAtTime(bpm, now + rampTime);
- this.bpm = bpm;
- };
- p5.Metro.prototype.getBPM = function (tempo) {
- return this.clock.getRate() / this.tatums * 60;
- };
- p5.Metro.prototype._init = function () {
- this.metroTicks = 0;
- };
- // clear existing synced parts, add only this one
- p5.Metro.prototype.resetSync = function (part) {
- this.syncedParts = [part];
- };
- // push a new synced part to the array
- p5.Metro.prototype.pushSync = function (part) {
- this.syncedParts.push(part);
- };
- p5.Metro.prototype.start = function (timeFromNow) {
- var t = timeFromNow || 0;
- var now = p5sound.audiocontext.currentTime;
- this.clock.start(now + t);
- this.setBPM(this.bpm);
- };
- p5.Metro.prototype.stop = function (timeFromNow) {
- var t = timeFromNow || 0;
- var now = p5sound.audiocontext.currentTime;
- if (this.clock._oscillator) {
- this.clock.stop(now + t);
- }
- };
- p5.Metro.prototype.beatLength = function (tatums) {
- this.tatums = 1 / tatums / 4;
- };
- }(master, Tone_core_Clock);
- var looper;
- looper = function () {
- 'use strict';
- var p5sound = master;
- var bpm = 120;
- /**
- * Set the global tempo, in beats per minute, for all
- * p5.Parts. This method will impact all active p5.Parts.
- *
- * @param {Number} BPM Beats Per Minute
- * @param {Number} rampTime Seconds from now
- */
- p5.prototype.setBPM = function (BPM, rampTime) {
- bpm = BPM;
- for (var i in p5sound.parts) {
- p5sound.parts[i].setBPM(bpm, rampTime);
- }
- };
- /**
- * <p>A phrase is a pattern of musical events over time, i.e.
- * a series of notes and rests.</p>
- *
- * <p>Phrases must be added to a p5.Part for playback, and
- * each part can play multiple phrases at the same time.
- * For example, one Phrase might be a kick drum, another
- * could be a snare, and another could be the bassline.</p>
- *
- * <p>The first parameter is a name so that the phrase can be
- * modified or deleted later. The callback is a a function that
- * this phrase will call at every step—for example it might be
- * called <code>playNote(value){}</code>. The array determines
- * which value is passed into the callback at each step of the
- * phrase. It can be numbers, an object with multiple numbers,
- * or a zero (0) indicates a rest so the callback won't be called).</p>
- *
- * @class p5.Phrase
- * @constructor
- * @param {String} name Name so that you can access the Phrase.
- * @param {Function} callback The name of a function that this phrase
- * will call. Typically it will play a sound,
- * and accept two parameters: a time at which
- * to play the sound (in seconds from now),
- * and a value from the sequence array. The
- * time should be passed into the play() or
- * start() method to ensure precision.
- * @param {Array} sequence Array of values to pass into the callback
- * at each step of the phrase.
- * @example
- * <div><code>
- * var mySound, myPhrase, myPart;
- * var pattern = [1,0,0,2,0,2,0,0];
- * var msg = 'click to play';
- *
- * function preload() {
- * mySound = loadSound('assets/beatbox.mp3');
- * }
- *
- * function setup() {
- * noStroke();
- * fill(255);
- * textAlign(CENTER);
- * masterVolume(0.1);
- *
- * myPhrase = new p5.Phrase('bbox', makeSound, pattern);
- * myPart = new p5.Part();
- * myPart.addPhrase(myPhrase);
- * myPart.setBPM(60);
- * }
- *
- * function draw() {
- * background(0);
- * text(msg, width/2, height/2);
- * }
- *
- * function makeSound(time, playbackRate) {
- * mySound.rate(playbackRate);
- * mySound.play(time);
- * }
- *
- * function mouseClicked() {
- * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
- * myPart.start();
- * msg = 'playing pattern';
- * }
- * }
- *
- * </code></div>
- */
- p5.Phrase = function (name, callback, sequence) {
- this.phraseStep = 0;
- this.name = name;
- this.callback = callback;
- /**
- * Array of values to pass into the callback
- * at each step of the phrase. Depending on the callback
- * function's requirements, these values may be numbers,
- * strings, or an object with multiple parameters.
- * Zero (0) indicates a rest.
- *
- * @property sequence
- * @type {Array}
- */
- this.sequence = sequence;
- };
- /**
- * <p>A p5.Part plays back one or more p5.Phrases. Instantiate a part
- * with steps and tatums. By default, each step represents 1/16th note.</p>
- *
- * <p>See p5.Phrase for more about musical timing.</p>
- *
- * @class p5.Part
- * @constructor
- * @param {Number} [steps] Steps in the part
- * @param {Number} [tatums] Divisions of a beat (default is 1/16, a quarter note)
- * @example
- * <div><code>
- * var box, drum, myPart;
- * var boxPat = [1,0,0,2,0,2,0,0];
- * var drumPat = [0,1,1,0,2,0,1,0];
- * var msg = 'click to play';
- *
- * function preload() {
- * box = loadSound('assets/beatbox.mp3');
- * drum = loadSound('assets/drum.mp3');
- * }
- *
- * function setup() {
- * noStroke();
- * fill(255);
- * textAlign(CENTER);
- * masterVolume(0.1);
- *
- * var boxPhrase = new p5.Phrase('box', playBox, boxPat);
- * var drumPhrase = new p5.Phrase('drum', playDrum, drumPat);
- * myPart = new p5.Part();
- * myPart.addPhrase(boxPhrase);
- * myPart.addPhrase(drumPhrase);
- * myPart.setBPM(60);
- * masterVolume(0.1);
- * }
- *
- * function draw() {
- * background(0);
- * text(msg, width/2, height/2);
- * }
- *
- * function playBox(time, playbackRate) {
- * box.rate(playbackRate);
- * box.play(time);
- * }
- *
- * function playDrum(time, playbackRate) {
- * drum.rate(playbackRate);
- * drum.play(time);
- * }
- *
- * function mouseClicked() {
- * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
- * myPart.start();
- * msg = 'playing part';
- * }
- * }
- * </code></div>
- */
- p5.Part = function (steps, bLength) {
- this.length = steps || 0;
- // how many beats
- this.partStep = 0;
- this.phrases = [];
- this.isPlaying = false;
- this.noLoop();
- this.tatums = bLength || 0.0625;
- // defaults to quarter note
- this.metro = new p5.Metro();
- this.metro._init();
- this.metro.beatLength(this.tatums);
- this.metro.setBPM(bpm);
- p5sound.parts.push(this);
- this.callback = function () {
- };
- };
- /**
- * Set the tempo of this part, in Beats Per Minute.
- *
- * @method setBPM
- * @param {Number} BPM Beats Per Minute
- * @param {Number} [rampTime] Seconds from now
- */
- p5.Part.prototype.setBPM = function (tempo, rampTime) {
- this.metro.setBPM(tempo, rampTime);
- };
- /**
- * Returns the Beats Per Minute of this currently part.
- *
- * @method getBPM
- * @return {Number}
- */
- p5.Part.prototype.getBPM = function () {
- return this.metro.getBPM();
- };
- /**
- * Start playback of this part. It will play
- * through all of its phrases at a speed
- * determined by setBPM.
- *
- * @method start
- * @param {Number} [time] seconds from now
- */
- p5.Part.prototype.start = function (time) {
- if (!this.isPlaying) {
- this.isPlaying = true;
- this.metro.resetSync(this);
- var t = time || 0;
- this.metro.start(t);
- }
- };
- /**
- * Loop playback of this part. It will begin
- * looping through all of its phrases at a speed
- * determined by setBPM.
- *
- * @method loop
- * @param {Number} [time] seconds from now
- */
- p5.Part.prototype.loop = function (time) {
- this.looping = true;
- // rest onended function
- this.onended = function () {
- this.partStep = 0;
- };
- var t = time || 0;
- this.start(t);
- };
- /**
- * Tell the part to stop looping.
- *
- * @method noLoop
- */
- p5.Part.prototype.noLoop = function () {
- this.looping = false;
- // rest onended function
- this.onended = function () {
- this.stop();
- };
- };
- /**
- * Stop the part and cue it to step 0.
- *
- * @method stop
- * @param {Number} [time] seconds from now
- */
- p5.Part.prototype.stop = function (time) {
- this.partStep = 0;
- this.pause(time);
- };
- /**
- * Pause the part. Playback will resume
- * from the current step.
- *
- * @method pause
- * @param {Number} time seconds from now
- */
- p5.Part.prototype.pause = function (time) {
- this.isPlaying = false;
- var t = time || 0;
- this.metro.stop(t);
- };
- /**
- * Add a p5.Phrase to this Part.
- *
- * @method addPhrase
- * @param {p5.Phrase} phrase reference to a p5.Phrase
- */
- p5.Part.prototype.addPhrase = function (name, callback, array) {
- var p;
- if (arguments.length === 3) {
- p = new p5.Phrase(name, callback, array);
- } else if (arguments[0] instanceof p5.Phrase) {
- p = arguments[0];
- } else {
- throw 'invalid input. addPhrase accepts name, callback, array or a p5.Phrase';
- }
- this.phrases.push(p);
- // reset the length if phrase is longer than part's existing length
- if (p.sequence.length > this.length) {
- this.length = p.sequence.length;
- }
- };
- /**
- * Remove a phrase from this part, based on the name it was
- * given when it was created.
- *
- * @method removePhrase
- * @param {String} phraseName
- */
- p5.Part.prototype.removePhrase = function (name) {
- for (var i in this.phrases) {
- if (this.phrases[i].name === name) {
- this.phrases.splice(i, 1);
- }
- }
- };
- /**
- * Get a phrase from this part, based on the name it was
- * given when it was created. Now you can modify its array.
- *
- * @method getPhrase
- * @param {String} phraseName
- */
- p5.Part.prototype.getPhrase = function (name) {
- for (var i in this.phrases) {
- if (this.phrases[i].name === name) {
- return this.phrases[i];
- }
- }
- };
- /**
- * Get a phrase from this part, based on the name it was
- * given when it was created. Now you can modify its array.
- *
- * @method replaceSequence
- * @param {String} phraseName
- * @param {Array} sequence Array of values to pass into the callback
- * at each step of the phrase.
- */
- p5.Part.prototype.replaceSequence = function (name, array) {
- for (var i in this.phrases) {
- if (this.phrases[i].name === name) {
- this.phrases[i].sequence = array;
- }
- }
- };
- p5.Part.prototype.incrementStep = function (time) {
- if (this.partStep < this.length - 1) {
- this.callback(time);
- this.partStep += 1;
- } else {
- if (!this.looping && this.partStep == this.length - 1) {
- console.log('done');
- // this.callback(time);
- this.onended();
- }
- }
- };
- /**
- * Fire a callback function at every step.
- *
- * @method onStep
- * @param {Function} callback The name of the callback
- * you want to fire
- * on every beat/tatum.
- */
- p5.Part.prototype.onStep = function (callback) {
- this.callback = callback;
- };
- // ===============
- // p5.Score
- // ===============
- /**
- * A Score consists of a series of Parts. The parts will
- * be played back in order. For example, you could have an
- * A part, a B part, and a C part, and play them back in this order
- * <code>new p5.Score(a, a, b, a, c)</code>
- *
- * @class p5.Score
- * @constructor
- * @param {p5.Part} part(s) One or multiple parts, to be played in sequence.
- * @return {p5.Score}
- */
- p5.Score = function () {
- // for all of the arguments
- this.parts = [];
- this.currentPart = 0;
- var thisScore = this;
- for (var i in arguments) {
- this.parts[i] = arguments[i];
- this.parts[i].nextPart = this.parts[i + 1];
- this.parts[i].onended = function () {
- thisScore.resetPart(i);
- playNextPart(thisScore);
- };
- }
- this.looping = false;
- };
- p5.Score.prototype.onended = function () {
- if (this.looping) {
- // this.resetParts();
- this.parts[0].start();
- } else {
- this.parts[this.parts.length - 1].onended = function () {
- this.stop();
- this.resetParts();
- };
- }
- this.currentPart = 0;
- };
- /**
- * Start playback of the score.
- *
- * @method start
- */
- p5.Score.prototype.start = function () {
- this.parts[this.currentPart].start();
- this.scoreStep = 0;
- };
- /**
- * Stop playback of the score.
- *
- * @method stop
- */
- p5.Score.prototype.stop = function () {
- this.parts[this.currentPart].stop();
- this.currentPart = 0;
- this.scoreStep = 0;
- };
- /**
- * Pause playback of the score.
- *
- * @method pause
- */
- p5.Score.prototype.pause = function () {
- this.parts[this.currentPart].stop();
- };
- /**
- * Loop playback of the score.
- *
- * @method loop
- */
- p5.Score.prototype.loop = function () {
- this.looping = true;
- this.start();
- };
- /**
- * Stop looping playback of the score. If it
- * is currently playing, this will go into effect
- * after the current round of playback completes.
- *
- * @method noLoop
- */
- p5.Score.prototype.noLoop = function () {
- this.looping = false;
- };
- p5.Score.prototype.resetParts = function () {
- for (var i in this.parts) {
- this.resetPart(i);
- }
- };
- p5.Score.prototype.resetPart = function (i) {
- this.parts[i].stop();
- this.parts[i].partStep = 0;
- for (var p in this.parts[i].phrases) {
- this.parts[i].phrases[p].phraseStep = 0;
- }
- };
- /**
- * Set the tempo for all parts in the score
- *
- * @param {Number} BPM Beats Per Minute
- * @param {Number} rampTime Seconds from now
- */
- p5.Score.prototype.setBPM = function (bpm, rampTime) {
- for (var i in this.parts) {
- this.parts[i].setBPM(bpm, rampTime);
- }
- };
- function playNextPart(aScore) {
- aScore.currentPart++;
- if (aScore.currentPart >= aScore.parts.length) {
- aScore.scoreStep = 0;
- aScore.onended();
- } else {
- aScore.scoreStep = 0;
- aScore.parts[aScore.currentPart - 1].stop();
- aScore.parts[aScore.currentPart].start();
- }
- }
- }(master);
- var soundRecorder;
- soundRecorder = function () {
- 'use strict';
- var p5sound = master;
- var ac = p5sound.audiocontext;
- /**
- * <p>Record sounds for playback and/or to save as a .wav file.
- * The p5.SoundRecorder records all sound output from your sketch,
- * or can be assigned a specific source with setInput().</p>
- * <p>The record() method accepts a p5.SoundFile as a parameter.
- * When playback is stopped (either after the given amount of time,
- * or with the stop() method), the p5.SoundRecorder will send its
- * recording to that p5.SoundFile for playback.</p>
- *
- * @class p5.SoundRecorder
- * @constructor
- * @example
- * <div><code>
- * var mic, recorder, soundFile;
- * var state = 0;
- *
- * function setup() {
- * background(200);
- * // create an audio in
- * mic = new p5.AudioIn();
- *
- * // prompts user to enable their browser mic
- * mic.start();
- *
- * // create a sound recorder
- * recorder = new p5.SoundRecorder();
- *
- * // connect the mic to the recorder
- * recorder.setInput(mic);
- *
- * // this sound file will be used to
- * // playback & save the recording
- * soundFile = new p5.SoundFile();
- *
- * text('keyPress to record', 20, 20);
- * }
- *
- * function keyPressed() {
- * // make sure user enabled the mic
- * if (state === 0 && mic.enabled) {
- *
- * // record to our p5.SoundFile
- * recorder.record(soundFile);
- *
- * background(255,0,0);
- * text('Recording!', 20, 20);
- * state++;
- * }
- * else if (state === 1) {
- * background(0,255,0);
- *
- * // stop recorder and
- * // send result to soundFile
- * recorder.stop();
- *
- * text('Stopped', 20, 20);
- * state++;
- * }
- *
- * else if (state === 2) {
- * soundFile.play(); // play the result!
- * save(soundFile, 'mySound.wav');
- * state++;
- * }
- * }
- * </div></code>
- */
- p5.SoundRecorder = function () {
- this.input = ac.createGain();
- this.output = ac.createGain();
- this.recording = false;
- this.bufferSize = 1024;
- this._channels = 2;
- // stereo (default)
- this._clear();
- // initialize variables
- this._jsNode = ac.createScriptProcessor(this.bufferSize, this._channels, 2);
- this._jsNode.onaudioprocess = this._audioprocess.bind(this);
- /**
- * callback invoked when the recording is over
- * @private
- * @type {function(Float32Array)}
- */
- this._callback = function () {
- };
- // connections
- this._jsNode.connect(p5.soundOut._silentNode);
- this.setInput();
- // add this p5.SoundFile to the soundArray
- p5sound.soundArray.push(this);
- };
- /**
- * Connect a specific device to the p5.SoundRecorder.
- * If no parameter is given, p5.SoundRecorer will record
- * all audible p5.sound from your sketch.
- *
- * @method setInput
- * @param {Object} [unit] p5.sound object or a web audio unit
- * that outputs sound
- */
- p5.SoundRecorder.prototype.setInput = function (unit) {
- this.input.disconnect();
- this.input = null;
- this.input = ac.createGain();
- this.input.connect(this._jsNode);
- this.input.connect(this.output);
- if (unit) {
- unit.connect(this.input);
- } else {
- p5.soundOut.output.connect(this.input);
- }
- };
- /**
- * Start recording. To access the recording, provide
- * a p5.SoundFile as the first parameter. The p5.SoundRecorder
- * will send its recording to that p5.SoundFile for playback once
- * recording is complete. Optional parameters include duration
- * (in seconds) of the recording, and a callback function that
- * will be called once the complete recording has been
- * transfered to the p5.SoundFile.
- *
- * @method record
- * @param {p5.SoundFile} soundFile p5.SoundFile
- * @param {Number} [duration] Time (in seconds)
- * @param {Function} [callback] The name of a function that will be
- * called once the recording completes
- */
- p5.SoundRecorder.prototype.record = function (sFile, duration, callback) {
- this.recording = true;
- if (duration) {
- this.sampleLimit = Math.round(duration * ac.sampleRate);
- }
- if (sFile && callback) {
- this._callback = function () {
- this.buffer = this._getBuffer();
- sFile.setBuffer(this.buffer);
- callback();
- };
- } else if (sFile) {
- this._callback = function () {
- this.buffer = this._getBuffer();
- sFile.setBuffer(this.buffer);
- };
- }
- };
- /**
- * Stop the recording. Once the recording is stopped,
- * the results will be sent to the p5.SoundFile that
- * was given on .record(), and if a callback function
- * was provided on record, that function will be called.
- *
- * @method stop
- */
- p5.SoundRecorder.prototype.stop = function () {
- this.recording = false;
- this._callback();
- this._clear();
- };
- p5.SoundRecorder.prototype._clear = function () {
- this._leftBuffers = [];
- this._rightBuffers = [];
- this.recordedSamples = 0;
- this.sampleLimit = null;
- };
- /**
- * internal method called on audio process
- *
- * @private
- * @param {AudioProcessorEvent} event
- */
- p5.SoundRecorder.prototype._audioprocess = function (event) {
- if (this.recording === false) {
- return;
- } else if (this.recording === true) {
- // if we are past the duration, then stop... else:
- if (this.sampleLimit && this.recordedSamples >= this.sampleLimit) {
- this.stop();
- } else {
- // get channel data
- var left = event.inputBuffer.getChannelData(0);
- var right = event.inputBuffer.getChannelData(1);
- // clone the samples
- this._leftBuffers.push(new Float32Array(left));
- this._rightBuffers.push(new Float32Array(right));
- this.recordedSamples += this.bufferSize;
- }
- }
- };
- p5.SoundRecorder.prototype._getBuffer = function () {
- var buffers = [];
- buffers.push(this._mergeBuffers(this._leftBuffers));
- buffers.push(this._mergeBuffers(this._rightBuffers));
- return buffers;
- };
- p5.SoundRecorder.prototype._mergeBuffers = function (channelBuffer) {
- var result = new Float32Array(this.recordedSamples);
- var offset = 0;
- var lng = channelBuffer.length;
- for (var i = 0; i < lng; i++) {
- var buffer = channelBuffer[i];
- result.set(buffer, offset);
- offset += buffer.length;
- }
- return result;
- };
- p5.SoundRecorder.prototype.dispose = function () {
- this._clear();
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- this._callback = function () {
- };
- if (this.input) {
- this.input.disconnect();
- }
- this.input = null;
- this._jsNode = null;
- };
- /**
- * Save a p5.SoundFile as a .wav audio file.
- *
- * @method saveSound
- * @param {p5.SoundFile} soundFile p5.SoundFile that you wish to save
- * @param {String} name name of the resulting .wav file.
- */
- p5.prototype.saveSound = function (soundFile, name) {
- var leftChannel, rightChannel;
- leftChannel = soundFile.buffer.getChannelData(0);
- // handle mono files
- if (soundFile.buffer.numberOfChannels > 1) {
- rightChannel = soundFile.buffer.getChannelData(1);
- } else {
- rightChannel = leftChannel;
- }
- var interleaved = interleave(leftChannel, rightChannel);
- // create the buffer and view to create the .WAV file
- var buffer = new ArrayBuffer(44 + interleaved.length * 2);
- var view = new DataView(buffer);
- // write the WAV container,
- // check spec at: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
- // RIFF chunk descriptor
- writeUTFBytes(view, 0, 'RIFF');
- view.setUint32(4, 36 + interleaved.length * 2, true);
- writeUTFBytes(view, 8, 'WAVE');
- // FMT sub-chunk
- writeUTFBytes(view, 12, 'fmt ');
- view.setUint32(16, 16, true);
- view.setUint16(20, 1, true);
- // stereo (2 channels)
- view.setUint16(22, 2, true);
- view.setUint32(24, 44100, true);
- view.setUint32(28, 44100 * 4, true);
- view.setUint16(32, 4, true);
- view.setUint16(34, 16, true);
- // data sub-chunk
- writeUTFBytes(view, 36, 'data');
- view.setUint32(40, interleaved.length * 2, true);
- // write the PCM samples
- var lng = interleaved.length;
- var index = 44;
- var volume = 1;
- for (var i = 0; i < lng; i++) {
- view.setInt16(index, interleaved[i] * (32767 * volume), true);
- index += 2;
- }
- p5.prototype.writeFile([view], name, 'wav');
- };
- // helper methods to save waves
- function interleave(leftChannel, rightChannel) {
- var length = leftChannel.length + rightChannel.length;
- var result = new Float32Array(length);
- var inputIndex = 0;
- for (var index = 0; index < length;) {
- result[index++] = leftChannel[inputIndex];
- result[index++] = rightChannel[inputIndex];
- inputIndex++;
- }
- return result;
- }
- function writeUTFBytes(view, offset, string) {
- var lng = string.length;
- for (var i = 0; i < lng; i++) {
- view.setUint8(offset + i, string.charCodeAt(i));
- }
- }
- }(sndcore, master);
- var peakdetect;
- peakdetect = function () {
- 'use strict';
- var p5sound = master;
- /**
- * <p>PeakDetect works in conjunction with p5.FFT to
- * look for onsets in some or all of the frequency spectrum.
- * </p>
- * <p>
- * To use p5.PeakDetect, call <code>update</code> in the draw loop
- * and pass in a p5.FFT object.
- * </p>
- * <p>
- * You can listen for a specific part of the frequency spectrum by
- * setting the range between <code>freq1</code> and <code>freq2</code>.
- * </p>
- *
- * <p><code>threshold</code> is the threshold for detecting a peak,
- * scaled between 0 and 1. It is logarithmic, so 0.1 is half as loud
- * as 1.0.</p>
- *
- * <p>
- * The update method is meant to be run in the draw loop, and
- * <b>frames</b> determines how many loops must pass before
- * another peak can be detected.
- * For example, if the frameRate() = 60, you could detect the beat of a
- * 120 beat-per-minute song with this equation:
- * <code> framesPerPeak = 60 / (estimatedBPM / 60 );</code>
- * </p>
- *
- * <p>
- * Based on example contribtued by @b2renger, and a simple beat detection
- * explanation by <a
- * href="http://www.airtightinteractive.com/2013/10/making-audio-reactive-visuals/"
- * target="_blank">Felix Turner</a>.
- * </p>
- *
- * @class p5.PeakDetect
- * @constructor
- * @param {Number} [freq1] lowFrequency - defaults to 20Hz
- * @param {Number} [freq2] highFrequency - defaults to 20000 Hz
- * @param {Number} [threshold] Threshold for detecting a beat between 0 and 1
- * scaled logarithmically where 0.1 is 1/2 the loudness
- * of 1.0. Defaults to 0.35.
- * @param {Number} [framesPerPeak] Defaults to 20.
- * @example
- * <div><code>
- *
- * var cnv, soundFile, fft, peakDetect;
- * var ellipseWidth = 10;
- *
- * function setup() {
- * background(0);
- * noStroke();
- * fill(255);
- * textAlign(CENTER);
- *
- * soundFile = loadSound('assets/beat.mp3');
- *
- * // p5.PeakDetect requires a p5.FFT
- * fft = new p5.FFT();
- * peakDetect = new p5.PeakDetect();
- *
- * }
- *
- * function draw() {
- * background(0);
- * text('click to play/pause', width/2, height/2);
- *
- * // peakDetect accepts an fft post-analysis
- * fft.analyze();
- * peakDetect.update(fft);
- *
- * if ( peakDetect.isDetected ) {
- * ellipseWidth = 50;
- * } else {
- * ellipseWidth *= 0.95;
- * }
- *
- * ellipse(width/2, height/2, ellipseWidth, ellipseWidth);
- * }
- *
- * // toggle play/stop when canvas is clicked
- * function mouseClicked() {
- * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
- * if (soundFile.isPlaying() ) {
- * soundFile.stop();
- * } else {
- * soundFile.play();
- * }
- * }
- * }
- * </code></div>
- */
- p5.PeakDetect = function (freq1, freq2, threshold, _framesPerPeak) {
- var framesPerPeak;
- // framesPerPeak determines how often to look for a beat.
- // If a beat is provided, try to look for a beat based on bpm
- this.framesPerPeak = _framesPerPeak || 20;
- this.framesSinceLastPeak = 0;
- this.decayRate = 0.95;
- this.threshold = threshold || 0.35;
- this.cutoff = 0;
- // how much to increase the cutoff
- // TO DO: document this / figure out how to make it accessible
- this.cutoffMult = 1.5;
- this.energy = 0;
- this.penergy = 0;
- // TO DO: document this property / figure out how to make it accessible
- this.currentValue = 0;
- /**
- * isDetected is set to true when a peak is detected.
- *
- * @attribute isDetected
- * @type {Boolean}
- * @default false
- */
- this.isDetected = false;
- this.f1 = freq1 || 40;
- this.f2 = freq2 || 20000;
- // function to call when a peak is detected
- this._onPeak = function () {
- };
- };
- /**
- * The update method is run in the draw loop.
- *
- * Accepts an FFT object. You must call .analyze()
- * on the FFT object prior to updating the peakDetect
- * because it relies on a completed FFT analysis.
- *
- * @method update
- * @param {p5.FFT} fftObject A p5.FFT object
- */
- p5.PeakDetect.prototype.update = function (fftObject) {
- var nrg = this.energy = fftObject.getEnergy(this.f1, this.f2) / 255;
- if (nrg > this.cutoff && nrg > this.threshold && nrg - this.penergy > 0) {
- // trigger callback
- this._onPeak();
- this.isDetected = true;
- // debounce
- this.cutoff = nrg * this.cutoffMult;
- this.framesSinceLastPeak = 0;
- } else {
- this.isDetected = false;
- if (this.framesSinceLastPeak <= this.framesPerPeak) {
- this.framesSinceLastPeak++;
- } else {
- this.cutoff *= this.decayRate;
- this.cutoff = Math.max(this.cutoff, this.threshold);
- }
- }
- this.currentValue = nrg;
- this.penergy = nrg;
- };
- /**
- * onPeak accepts two arguments: a function to call when
- * a peak is detected. The value of the peak,
- * between 0.0 and 1.0, is passed to the callback.
- *
- * @method onPeak
- * @param {Function} callback Name of a function that will
- * be called when a peak is
- * detected.
- * @param {Object} [val] Optional value to pass
- * into the function when
- * a peak is detected.
- * @example
- * <div><code>
- * var cnv, soundFile, fft, peakDetect;
- * var ellipseWidth = 0;
- *
- * function setup() {
- * cnv = createCanvas(100,100);
- * textAlign(CENTER);
- *
- * soundFile = loadSound('assets/beat.mp3');
- * fft = new p5.FFT();
- * peakDetect = new p5.PeakDetect();
- *
- * setupSound();
- *
- * // when a beat is detected, call triggerBeat()
- * peakDetect.onPeak(triggerBeat);
- * }
- *
- * function draw() {
- * background(0);
- * fill(255);
- * text('click to play', width/2, height/2);
- *
- * fft.analyze();
- * peakDetect.update(fft);
- *
- * ellipseWidth *= 0.95;
- * ellipse(width/2, height/2, ellipseWidth, ellipseWidth);
- * }
- *
- * // this function is called by peakDetect.onPeak
- * function triggerBeat() {
- * ellipseWidth = 50;
- * }
- *
- * // mouseclick starts/stops sound
- * function setupSound() {
- * cnv.mouseClicked( function() {
- * if (soundFile.isPlaying() ) {
- * soundFile.stop();
- * } else {
- * soundFile.play();
- * }
- * });
- * }
- * </code></div>
- */
- p5.PeakDetect.prototype.onPeak = function (callback, val) {
- var self = this;
- self._onPeak = function () {
- callback(self.energy, val);
- };
- };
- }(master);
- var gain;
- gain = function () {
- 'use strict';
- var p5sound = master;
- /**
- * A gain node is usefull to set the relative volume of sound.
- * It's typically used to build mixers.
- *
- * @class p5.Gain
- * @constructor
- * @example
- * <div><code>
- *
- * // load two soundfile and crossfade beetween them
- * var sound1,sound2;
- * var gain1, gain2, gain3;
- *
- * function preload(){
- * soundFormats('ogg', 'mp3');
- * sound1 = loadSound('../_files/Damscray_-_Dancing_Tiger_01');
- * sound2 = loadSound('../_files/beat.mp3');
- * }
- *
- * function setup() {
- * createCanvas(400,200);
- *
- * // create a 'master' gain to which we will connect both soundfiles
- * gain3 = new p5.Gain();
- * gain3.connect();
- *
- * // setup first sound for playing
- * sound1.rate(1);
- * sound1.loop();
- * sound1.disconnect(); // diconnect from p5 output
- *
- * gain1 = new p5.Gain(); // setup a gain node
- * gain1.setInput(sound1); // connect the first sound to its input
- * gain1.connect(gain3); // connect its output to the 'master'
- *
- * sound2.rate(1);
- * sound2.disconnect();
- * sound2.loop();
- *
- * gain2 = new p5.Gain();
- * gain2.setInput(sound2);
- * gain2.connect(gain3);
- *
- * }
- *
- * function draw(){
- * background(180);
- *
- * // calculate the horizontal distance beetween the mouse and the right of the screen
- * var d = dist(mouseX,0,width,0);
- *
- * // map the horizontal position of the mouse to values useable for volume control of sound1
- * var vol1 = map(mouseX,0,width,0,1);
- * var vol2 = 1-vol1; // when sound1 is loud, sound2 is quiet and vice versa
- *
- * gain1.amp(vol1,0.5,0);
- * gain2.amp(vol2,0.5,0);
- *
- * // map the vertical position of the mouse to values useable for 'master volume control'
- * var vol3 = map(mouseY,0,height,0,1);
- * gain3.amp(vol3,0.5,0);
- * }
- *</code></div>
- *
- */
- p5.Gain = function () {
- this.ac = p5sound.audiocontext;
- this.input = this.ac.createGain();
- this.output = this.ac.createGain();
- // otherwise, Safari distorts
- this.input.gain.value = 0.5;
- this.input.connect(this.output);
- // add to the soundArray
- p5sound.soundArray.push(this);
- };
- /**
- * Connect a source to the gain node.
- *
- * @method setInput
- * @param {Object} src p5.sound / Web Audio object with a sound
- * output.
- */
- p5.Gain.prototype.setInput = function (src) {
- src.connect(this.input);
- };
- /**
- * Send output to a p5.sound or web audio object
- *
- * @method connect
- * @param {Object} unit
- */
- p5.Gain.prototype.connect = function (unit) {
- var u = unit || p5.soundOut.input;
- this.output.connect(u.input ? u.input : u);
- };
- /**
- * Disconnect all output.
- *
- * @method disconnect
- */
- p5.Gain.prototype.disconnect = function () {
- this.output.disconnect();
- };
- /**
- * Set the output level of the gain node.
- *
- * @method amp
- * @param {Number} volume amplitude between 0 and 1.0
- * @param {Number} [rampTime] create a fade that lasts rampTime
- * @param {Number} [timeFromNow] schedule this event to happen
- * seconds from now
- */
- p5.Gain.prototype.amp = function (vol, rampTime, tFromNow) {
- var rampTime = rampTime || 0;
- var tFromNow = tFromNow || 0;
- var now = p5sound.audiocontext.currentTime;
- var currentVol = this.output.gain.value;
- this.output.gain.cancelScheduledValues(now);
- this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow);
- this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime);
- };
- p5.Gain.prototype.dispose = function () {
- // remove reference from soundArray
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- this.output.disconnect();
- this.input.disconnect();
- this.output = undefined;
- this.input = undefined;
- };
- }(master, sndcore);
- var distortion;
- distortion = function () {
- 'use strict';
- var p5sound = master;
- /*
- * Adapted from [Kevin Ennis on StackOverflow](http://stackoverflow.com/questions/22312841/waveshaper-node-in-webaudio-how-to-emulate-distortion)
- */
- function makeDistortionCurve(amount) {
- var k = typeof amount === 'number' ? amount : 50;
- var n_samples = 44100;
- var curve = new Float32Array(n_samples);
- var deg = Math.PI / 180;
- var i = 0;
- var x;
- for (; i < n_samples; ++i) {
- x = i * 2 / n_samples - 1;
- curve[i] = (3 + k) * x * 20 * deg / (Math.PI + k * Math.abs(x));
- }
- return curve;
- }
- /**
- * A Distortion effect created with a Waveshaper Node,
- * with an approach adapted from
- * [Kevin Ennis](http://stackoverflow.com/questions/22312841/waveshaper-node-in-webaudio-how-to-emulate-distortion)
- *
- * @class p5.Distortion
- * @constructor
- * @param {Number} [amount=0.25] Unbounded distortion amount.
- * Normal values range from 0-1.
- * @param {String} [oversample='none'] 'none', '2x', or '4x'.
- *
- * @return {Object} Distortion object
- */
- p5.Distortion = function (amount, oversample) {
- if (typeof amount === 'undefined') {
- amount = 0.25;
- }
- if (typeof amount !== 'number') {
- throw new Error('amount must be a number');
- }
- if (typeof oversample === 'undefined') {
- oversample = '2x';
- }
- if (typeof oversample !== 'string') {
- throw new Error('oversample must be a String');
- }
- var curveAmount = p5.prototype.map(amount, 0, 1, 0, 2000);
- this.ac = p5sound.audiocontext;
- this.input = this.ac.createGain();
- this.output = this.ac.createGain();
- /**
- * The p5.Distortion is built with a
- * <a href="http://www.w3.org/TR/webaudio/#WaveShaperNode">
- * Web Audio WaveShaper Node</a>.
- *
- * @property WaveShaperNode
- * @type {Object} AudioNode
- */
- this.waveShaperNode = this.ac.createWaveShaper();
- this.amount = curveAmount;
- this.waveShaperNode.curve = makeDistortionCurve(curveAmount);
- this.waveShaperNode.oversample = oversample;
- this.input.connect(this.waveShaperNode);
- this.waveShaperNode.connect(this.output);
- this.connect();
- // add to the soundArray
- p5sound.soundArray.push(this);
- };
- p5.Distortion.prototype.process = function (src, amount, oversample) {
- src.connect(this.input);
- this.set(amount, oversample);
- };
- /**
- * Set the amount and oversample of the waveshaper distortion.
- *
- * @method setType
- * @param {Number} [amount=0.25] Unbounded distortion amount.
- * Normal values range from 0-1.
- * @param {String} [oversample='none'] 'none', '2x', or '4x'.
- */
- p5.Distortion.prototype.set = function (amount, oversample) {
- if (amount) {
- var curveAmount = p5.prototype.map(amount, 0, 1, 0, 2000);
- this.amount = curveAmount;
- this.waveShaperNode.curve = makeDistortionCurve(curveAmount);
- }
- if (oversample) {
- this.waveShaperNode.oversample = oversample;
- }
- };
- /**
- * Return the distortion amount, typically between 0-1.
- *
- * @method getAmount
- * @return {Number} Unbounded distortion amount.
- * Normal values range from 0-1.
- */
- p5.Distortion.prototype.getAmount = function () {
- return this.amount;
- };
- /**
- * Return the oversampling.
- *
- * @return {String} Oversample can either be 'none', '2x', or '4x'.
- */
- p5.Distortion.prototype.getOversample = function () {
- return this.waveShaperNode.oversample;
- };
- /**
- * Send output to a p5.sound or web audio object
- *
- * @method connect
- * @param {Object} unit
- */
- p5.Distortion.prototype.connect = function (unit) {
- var u = unit || p5.soundOut.input;
- this.output.connect(u);
- };
- /**
- * Disconnect all output.
- *
- * @method disconnect
- */
- p5.Distortion.prototype.disconnect = function () {
- this.output.disconnect();
- };
- p5.Distortion.prototype.dispose = function () {
- var index = p5sound.soundArray.indexOf(this);
- p5sound.soundArray.splice(index, 1);
- this.input.disconnect();
- this.waveShaperNode.disconnect();
- this.input = null;
- this.waveShaperNode = null;
- if (typeof this.output !== 'undefined') {
- this.output.disconnect();
- this.output = null;
- }
- };
- }(master);
- var src_app;
- src_app = function () {
- 'use strict';
- var p5SOUND = sndcore;
- return p5SOUND;
- }(sndcore, master, helpers, errorHandler, panner, soundfile, amplitude, fft, signal, oscillator, env, pulse, noise, audioin, filter, delay, reverb, metro, looper, soundRecorder, peakdetect, gain, distortion);
- }));
|