123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586 |
- <!doctype html>
- <!--
- Hello! If you're seeing this, you're probably using your browser's
- "view-source" feature. We've tried to make the output here readable,
- but you may have better luck looking at the source files in our git
- repository [1]
- - https://gitlab.com/lmcnulty/gender-voice-visualization
- - https://github.com/lmcnulty/gender-voice-visualization
- The source is free software under the GNU Affero General Public License [1],
- and assets under a Creative Commons Attribution Sharealike license.
- [1] https://gitlab.com/lmcnulty/gender-voice-visualization,
- https://github.com/lmcnulty/gender-voice-visualization
- [2] https://www.gnu.org/licenses/agpl-3.0.en.html
- [3] https://creativecommons.org/licenses/by-sa/4.0/
- -->
- <html>
- <head>
- <meta charset='utf-8'/>
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="theme-color" content="#45acf4">
- <title>Acoustic Genderspace Viewer - Research Trial</title>
- <link rel="shortcut icon" href="./resources/logo.png">
- <link rel="icon" sizes="192x192" href="./resources/logo.png">
- <link href="data:text/css," rel="stylesheet" type="text/css" title="Null">
- <link rel="manifest" href="./manifest.json">
- <style>
-
-
- /* ======================= Variables ======================= */
- body {
- --header-height: 2.5rem;
-
-
- --body-background: #5e5e5e;
-
- --body-color: #f2f2f2;
-
- --chrome-color: #f2f2f2;
-
- --chrome-background: #565656;
-
- --chrome-border: #181818;
-
- --accent: #169CF9;
-
- --accent-text: #ffffff;
-
- --button-color: #ffffff;
-
- --button-background: #474747;
-
- --input-color: #f2f2f2;
-
- --input-background: #707070;
-
- --border-radius: 8;
-
- --box-shadow-strength: 8;
-
- --box-shadow-spread: 40;
-
- --highlight-strength: 9;
-
- --box-shadow: -1px 1px var(--box-shadow-spread) var(--box-shadow-spread) rgba(0,0,0,calc(var(--box-shadow-strength)));
- --progress-bar-background: var(--accent);
- }
-
- /* ======================= Layout ======================= */
- * {
- box-sizing: border-box;
- }
- html, body {
- position: relative;
- margin: 0px;
- overflow: hidden;
- background: var(--body-background);
- color: var(--body-color);
- font-family: sans-serif;
- }
- html {
- height: 100%;
- }
- body { height: 100%; }
- header, footer {
- height: var(--header-height);
- width: 100%;
- padding: .5rem;
- display: flex;
- position: relative;
- align-items: center;
- background: var(--chrome-background);
- color: var(--chrome-color);
- border-color: var(--chrome-border);
- }
- main {
- height: calc(100% - var(--header-height));
- display: flex;
- justify-content: center;
- }
- header button, footer button {
- height: calc(var(--header-height) - 1rem);
- flex-shrink: 0;
- line-height: 1rem;
- min-width: 2rem;
- }
- button span {
- display: inline-block;
- vertical-align: bottom;
- }
- button svg + span {
- margin-left: .25em;
- }
- button *{
- height: 100%;
- }
- .spacer {
- height: 1px;
- width: 100%;
- flex-shrink: 1;
- }
- header > * + *, footer > * + *{
- margin-left: .5rem;
- }
- header > * {
- z-index: 2;
- }
- main > section {
- position: relative;
- border-top: 1px solid var(--chrome-border);
- }
- main > aside {
- position: relative;
- color: var(--chrome-color);
- background: var(--chrome-background);
- }
- input, select {
- max-width: 100%;
- }
- audio {
- width: 100%;
- }
- #offscreen {
- display: none;
- }
- section > *:first-child {
- margin-top: 0px;
- }
- .wide-body main > section {
- max-width: calc(100vh - var(--header-height));
- max-height: 100vw;
- width: 100%;
- height: 100%;
- }
- .wide-body main > aside {
- width: 100%;
- max-width: calc(100vw - 100vh + var(--header-height));
- }
- .narrow-body main {
- flex-direction: column;
- }
- .narrow-body aside, .narrow-body .current-tab {
- border-left: 0px;
- }
- .narrow-body main aside {
- max-width: 100%;
- height: calc(100% - 100vw - var(--header-height));
- }
- .narrow-body voice-graph-2d {
- max-width: calc(60vh - var(--header-height) * .6);
- max-height: 100%;
- margin: auto;
- bottom: 0px;
- position: relative;
- }
- .narrow-body main > section {
- max-height: 60%;
- height: 100vw;
- flex-shrink: 0;
- }
- .narrow-body main > aside {
- max-height: calc(100% - 100vw);
- min-height: 40%;
- height: 100%;
- }
- .narrow-body .loading-message {
- display: none !important;
- }
- section > h2:first-child {
- margin-top: 0px;
- margin-bottom: 0px;
- }
- section > h2:first-child + p {
- margin-top: 0px;
- }
- voice-graph-2d {
- position: absolute;
- top: 0px;
- bottom: 0px;
- width: 100%;
- }
- .hidden {
- display: none;
- }
- .scrolling-text-box {
- overflow-y: auto;
- height: 6em;
- border: 1px solid var(--chrome-border);
- padding: 1em;
- }
- .scrolling-text-box *:first-child {
- margin-top: 0px;
- }
- .scrolling-text-box *:last-child {
- margin-bottom: 0px;
- }
- /*
- input, button, select, textarea {
- font-size: inherit;
- }
- */
-
- /* ======================= Style ======================= */
- button:hover, input:hover, select:hover, textArea:hover {
- opacity: .8;
- }
- button:active {
- opacity: .5;
- }
- *:focus {
- box-shadow: 0px 0px 2px 2px var(--accent) !important;
- }
- button:hover:disabled {
- opacity: .5;
- }
- *:disabled {
- opacity: .5;
- }
- a {
- color: var(--accent);
- }
- .custom-widgets button,
- .custom-widgets input[type=submit]
- {
- padding-top: 0px;
- padding-bottom: 0px;
- vertical-align: middle;
- background: var(--button-background);
- color: var(--button-color);
- border: 1px solid var(--chrome-border);
- border-radius: var(--border-radius);
- box-shadow: var(--box-shadow);
- }
- .custom-widgets input[type=file]::file-selector-button {
- padding-top: 0px;
- padding-bottom: 0px;
- vertical-align: middle;
- background: var(--button-background);
- color: var(--button-color);
- border: 1px solid var(--chrome-border);
- border-radius: var(--border-radius);
- box-shadow: var(--box-shadow);
- }
- .custom-widgets textarea, .custom-widgets select, .custom-widgets input {
- background: var(--input-background);
- color: var(--input-color);
- border: 1px solid var(--chrome-border);
- border-radius: var(--border-radius);
- padding: .25em;
- }
- main > section {
- box-shadow: inset var(--box-shadow);
- }
- button,
- input[type=submit]
- {
- height: calc(var(--header-height) - 1rem);
- }
- input[type=file]::file-selector-button {
- height: calc(var(--header-height) - 1rem);
- }
- input, textarea, progress[value], select {
- box-shadow: inset var(--box-shadow);
- }
- button, input[type=submit] {
- position: relative;
- }
- h2 {
- font-size: 100%;
- }
-
- .custom-widgets button:after,
- .custom-widgets input[type=submit]:after
- {
- content: ' ';
- position: absolute;
- bottom: 0px;
- left: 0px;
- right: 0px;
- top: 0px;
- box-shadow: inset -1px 2px calc(var(--box-shadow-spread) * 1.25) 2px rgba(255,255,255, calc(var(--highlight-strength) * 1));
- border-radius: var(--border-radius);
- }
- .tab-set button:after {
- content: none;
- }
- voice-graph-2d canvas {
- border-radius: var(--border-radius);
- }
- textarea {
- width: 100%;
- height: 8em;
- font-family: sans-serif;
- margin-top: 1em;
- margin-bottom: 1em;
- }
- .modal {
- border: 1px solid;
- padding: 1em;
- padding-bottom: 0em;
- border-radius: var(--border-radius);
- box-shadow: var(--box-shadow);
- }
- .modal section {
- padding-bottom: 2em;
- }
- .custom-widgets ::selection {
- background: var(--accent);
- color: white;
- color: var(--accent-text);
- }
- h2 {
- margin-bottom: .5em;
- }
- h2 + p {
- margin-top: .5em;
- }
- .scrolling-text-box {
- background: var(--body-background);
- color: var(--body-color);
- border-radius: var(--border-radius);
- box-shadow: inset var(--box-shadow);
- }
- .custom-widgets button.attention{
- background: var(--accent);
- color: var(--accent-text);
- font-weight: bold;
- }
- hr {
- border: 1px solid var(--chrome-border);
- }
-
- /* Voice Graph */
- * {
- --voice-graph-z-index: 0;
- }
- voice-graph-2d {
- display: block;
- --padding: 2em;
- }
- voice-graph-2d > canvas {
- position: relative;
- height: 100%;
- width: 100%;
- float: left;
- }
- voice-graph-2d {
- padding: var(--padding);
- overflow: hidden;
- }
- voice-graph-2d > .axis-labels {
- display: flex;
- justify-content: space-between;
- position: absolute;
- line-height: var(--padding);
- }
- voice-graph-2d > .axis-labels > .title {
- font-weight: bold;
- }
- voice-graph-2d > .x.axis-labels {
- top: 0px;
- left: var(--padding);
- right: var(--padding);
- }
- voice-graph-2d > .x-opposite.axis-labels {
- bottom: 0px;
- left: var(--padding);
- right: var(--padding);
- }
- .current-value {
- z-index: calc(var(--voice-graph-z-index) + 3);
- text-shadow: var(--body-background) 0px 1px, var(--body-background) 0px -1px, var(--body-background) 1px 0px, var(--body-background) -1px 0px;
- }
- voice-graph-2d > .y-opposite.axis-labels {
- top: var(--padding);
- bottom: var(--padding);
- left: 0px;
- writing-mode: vertical-rl;
- text-orientation: mixed;
- }
- voice-graph-2d > .y.axis-labels {
- top: var(--padding);
- bottom: var(--padding);
- right: 0px;
- writing-mode: vertical-lr;
- text-orientation: sideways-left;
- }
- voice-graph-2d > .overlay {
- position: absolute;
- top: var(--padding);
- left: var(--padding);
- bottom: var(--padding);
- right: var(--padding);
- overflow: visible;
- }
- voice-graph-2d > .overlay > .marker {
- display: online-block;
- height: 1.5em;
- width: 1.5em;
- border-radius: 50%;
- background: black;
- border: .1em solid white;
- position: absolute;
- z-index: calc(var(--voice-graph-z-index) + 2);
- color: white;
- text-align: center;
- line-height: 1em;
- /*transition: all .1s;*/
- top: calc(-.75em);
- left: calc(-.75em);
- }
- .marker:hover {
- opacity: 1;
- }
- .custom-widgets button.marker:after {
- box-shadow: 0px;
- display: none;
- }
- /*voice-graph-2d > .overlay > .marker:hover {*/
- voice-graph-2d > .overlay > .marker.preview {
- }
- voice-graph-2d > .overlay > .marker.playing{
- border: 2px solid yellow;
- }
- voice-graph-2d > .overlay > .x.hairline {
- content: '';
- border-left: .1em dotted black;
- display: block;
- position: absolute;
- z-index: calc(var(--voice-graph-z-index) + 1);
- height: 100%;
- top: 0px;
- left: -1px;
- transition: .1s all;
- }
- voice-graph-2d > .overlay > .hairline.hidden {
- opacity: 0;
- }
- voice-graph-2d > .x-opposite.axis-labels > .current-value {
- position: absolute;
- width: calc(2 * var(--padding)) ;
- text-align: center;
- bottom: 0px;
- opacity: 0;
- left: calc(-1 * var(--padding));
- transition: .1s all;
- }
- voice-graph-2d > .overlay > .y.hairline {
- content: '';
- border-top: .1em dotted black;
- display: block;
- position: absolute;
- z-index: calc(var(--voice-graph-z-index) + 2);
- width: 100%;
- left: 0px;
- top: -1px;
- transition: .1s all;
- }
- voice-graph-2d > .y-opposite.axis-labels > .current-value {
- position: absolute;
- height: calc(2 * var(--padding));
- text-align: center;
- transform: rotate(180deg);
- transform-origin: center;
- left: 0px;
- bottom: calc(-1 * var(--padding));
- opacity: 0;
- transition: .1s all;
- }
-
- voice-graph-2d .marker > .infobox {
- position: absolute;
- width: 18ch;
- left: calc(0.5em - 9ch);
- top: calc(-1 * var(--padding));
- pointer-events: none;
- text-shadow: rgba(0,0,0,.5) 1px 1px, rgba(0,0,0,.5) -1px -1px, rgba(0,0,0,.5) 1px -1px, rgba(0,0,0,.5) -1px 1px;
- }
- .ratings-bar {
- box-sizing: border-box;
- display: flex;
- width: 100%;
- overflow: hidden;
- border-radius: 5px 5px 5px 5px;
- }
- .ratings-bar > span {
- padding: 2px 0px 2px 0px;
- display: inline-block;
- box-sizing: border-box;
- overflow: hidden;
- font-size: small;
- line-height: 1em;
- }
- .infobox .strongly-agree {
- background: #43db20;
- color: white;
- border-radius: 0px 5px 5px 0px;
- }
- .infobox .agree {
- background: #a5db99;
- color: white;
- }
- .infobox .disagree {
- background: #e28888;
- color: white;
- }
- .infobox .strongly-disagree{
- background: #e22222;
- color: white;
- border-radius: 5px 0px 0px 5px;
- }
- .clef {
- position: absolute;
- left: 0px;
- width: var(--padding);
- /*z-index: calc(var(--voice-graph-z-index) + 2);*/
- text-align: center;
- transform-origin: center center;
- transform: scale(2);
- padding: 0px;
- margin-bottom: -.5em;
- }
- .clef.treble { top: var(--padding); }
- .clef.bass { bottom: var(--padding); }
- .instrument {
- position: absolute;
- bottom: calc(var(--padding) * .1);
- height: calc(var(--padding) * .8);
- }
- .instrument svg {
- height: 100%;
- }
- .instrument.flute {
- right: var(--padding);
- }
- .instrument.tuba {
- left: var(--padding);
- }
- .spinner,
- .spinner:after {
- border-radius: 50%;
- width: 3em;
- height: 3em;
- position: relative;
- flex-shrink: 0;
- }
- .spinner {
- margin: 60px auto;
- font-size: 10px;
- position: relative;
- text-indent: -9999em;
- border-top: .5em solid var(--accent);
- border-right: .5em solid var(--accent);
- border-bottom: .5em solid var(--accent);
- border-left: .5em solid var(--button-background);
- -webkit-transform: translateZ(0);
- -ms-transform: translateZ(0);
- transform: translateZ(0);
- -webkit-animation: load8 1.1s infinite linear;
- animation: load8 1.1s infinite linear;
- }
- @-webkit-keyframes load8 {
- 0% {
- -webkit-transform: rotate(0deg);
- transform: rotate(0deg);
- }
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
- }
- @keyframes load8 {
- 0% {
- -webkit-transform: rotate(0deg);
- transform: rotate(0deg);
- }
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
- }
- header .spinner {
- margin-left: 1em;
- }
- header .spinner.hidden + .loading-message {
- display: none;
- }
- header .spinner + .loading-message {
- display: inline;
- flex-shrink: 0;
- }
-
- </style>
- <script>
-
- let darkIcon = String(`
- <svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- id="svg5181"
- version="1.1"
- viewBox="0 0 8.5024996 8.5054264"
- height="32.146492"
- width="32.135433">
- <defs
- id="defs5175" />
- <metadata
- id="metadata5178">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- transform="translate(-24.474941,-137.0211)"
- id="layer1">
- <rect style="fill:none;fill-opacity:1;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0"
- id="rect4546-3"
- width="7.9375"
- height="7.9375"
- x="24.757441"
- y="137.30507" />
- <path
- id="path4571"
- d="m 27.40326,137.30506 c -3.43959,1.32291 -3.43959,6.61458 0,7.9375"
- style="fill:none;stroke:currentColor;stroke-width:0.565;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
- <path
- style="fill:none;stroke:currentColor;stroke-width:0.565;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 27.93242,138.09881 c -2.64583,1.85208 -2.64583,4.49791 0,6.35"
- id="path4573" />
- <path
- style="fill:none;stroke:currentColor;stroke-width:0.565;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 30.0491,137.30506 c 3.43959,1.32291 3.43959,6.61458 0,7.9375"
- id="path5740" />
- <path
- id="path5742"
- d="m 29.51994,138.09881 c 2.64583,1.85208 2.64583,4.49791 0,6.35"
- style="fill:none;stroke:currentColor;stroke-width:0.565;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
- </g>
- </svg>
- `)
- let brightIcon = String(`
- <svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="32.135433"
- height="32.153236"
- viewBox="0 0 8.5025005 8.5072107"
- version="1.1"
- id="svg4565"
- inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
- sodipodi:docname="bright.svg">
- <defs id="defs4559">
- <link href="style.css" type="text/css" rel="stylesheet"
- xmlns="http://www.w3.org/1999/xhtml"/>
- </defs>
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="11.2"
- inkscape:cx="12.379905"
- inkscape:cy="11.113832"
- inkscape:document-units="mm"
- inkscape:current-layer="svg4565"
- showgrid="false"
- fit-margin-top="0"
- fit-margin-left="0"
- fit-margin-right="0"
- fit-margin-bottom="0"
- units="px"
- inkscape:window-width="1920"
- inkscape:window-height="1002"
- inkscape:window-x="0"
- inkscape:window-y="0"
- inkscape:window-maximized="1" />
- <metadata
- id="metadata4562">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(-32.790418,-144.57973)">
- <rect
- style="fill:none;fill-opacity:1;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0"
- id="rect4581"
- width="7.9375"
- height="7.9375"
- x="33.072918"
- y="144.86459"
- inkscape:export-xdpi="191.19"
- inkscape:export-ydpi="191.19" />
- <path
- sodipodi:nodetypes="cc"
- inkscape:connector-curvature="0"
- id="path4583"
- d="m 33.866667,144.86458 c 1.85208,1.32292 1.85208,6.61459 0,7.9375"
- style="fill:none;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
- <path
- style="fill:none;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 35.454167,145.39375 c 1.32291,1.85209 1.32291,5.02709 0,6.87917"
- id="path4585"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc" />
- <path
- style="fill:none;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 40.216657,144.86459 c -1.85208,1.32292 -1.85208,6.61459 0,7.9375"
- id="path833"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc" />
- <path
- sodipodi:nodetypes="cc"
- inkscape:connector-curvature="0"
- id="path835"
- d="m 38.629157,145.39376 c -1.32291,1.85209 -1.32291,5.02709 0,6.87917"
- style="fill:none;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
- </g>
- </svg>
- `)
- </script>
-
- <script src="./ui/dom.js"></script>
- <script src="./ui/globals.js"></script>
- <script src="./ui/clip.js"></script>
- <script src="./ui/examples.js"></script>
- <script src="./ui/util.js"></script>
-
- </head>
- <body class="narrow-body custom-widgets">
- <style>
- .popup {
- max-width: 500px;
- width: 400px;
- margin: 1em;
- margin-left: calc(-200px);
- padding: 1em;
- padding-right: 2em;
- color: black;
- background: white;
- border: 2px solid whitesmoke;
- box-shadow: 0px 0px 10px 10px rgba(0, 0, 0, .25);
- position: fixed;
- top: 0px;
- left: 50%;
- z-index: 99999999999999;
- transition: all 1s;
- border-radius: .25em;
- }
- .popup button.close {
- background: none;
- border: none;
- box-shadow: none;
- font-size: 200%;
- color: grey;
- position: absolute;
- top: 0px;
- right: 0px;
- }
- .popup.offscreen {
- top: -300px;
- opacity: 0;
- }
- </style>
- <div class="popup offscreen">
- Please complete <a href="https://brown.co1.qualtrics.com/jfe/form/SV_4PgW7EKNvnxnWLk">our survey</a>
- to help us in our research.
- <button class="close" onclick="$('.popup').classList.add('offscreen')">×</button>
- </div>
- <script>
- //setTimeout(() => $('.popup').classList.remove('offscreen'), 30000);
- </script>
- <header>
-
-
- <!-- ======================= Player ======================= -->
- <button class="play-pause iconic" title='play/pause'>
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-play-fill" viewBox="0 0 16 16">
- <path d="m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z"/>
- </svg>
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pause-fill" viewBox="0 0 16 16">
- <path d="M5.5 3.5A1.5 1.5 0 0 1 7 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5zm5 0A1.5 1.5 0 0 1 12 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5z"/>
- </svg>
- </button>
- <progress class="player" value="0" min="0"></progress>
- <audio class="clip"></audio>
- <script>
- try {
- let mediaElement = $('.clip');
- let playPauseButton = $('button.play-pause');
- let progressBar = $('progress.player');
- document.body.addEventListener('keyup', evt => {
-
- if (['INPUT', 'TEXTAREA'].filter(
- tag => tag == document.activeElement.tagName
- ).length == 0) {
- if (evt.code == 'ArrowRight') {
- globalState.set('playbackTime', Math.min(globalState.get('playbackTime') + 1, mediaElement.duration));
- }
- if (evt.code == 'ArrowLeft') {
- globalState.set('playbackTime', Math.max(globalState.get('playbackTime') - 1, 0));
- }
- }
- if (evt.code == 'Space' && ['INPUT', 'TEXTAREA', 'BUTTON', 'SUMMARY'].filter(
- tag => tag == document.activeElement.tagName
- ).length == 0) {
- globalState.set('playing', !globalState.get('playing'));
- }
- });
- globalState.render(['playingClip'], current => {
- if (current.playingClip && current.playingClip.audio) {
- progressBar.setAttribute('max', 60);
- mediaElement.addEventListener('loadedmetadata', updateDuration);
- mediaElement.addEventListener('durationchange', updateDuration);
- mediaElement.src = current.playingClip.audio;
- globalState.set('playbackTime', 0);
- }
- });
- function updateDuration(evt) {
- if (mediaElement.duration < 1000 && mediaElement.duration != Infinity) {
- progressBar.setAttribute('max', mediaElement.duration);
- };
- }
- globalState.render(['playingClip'], current => {
- progressBar.setAttribute('max', 60);
- if (current.playingClip && current.playingClip.audio) {
- mediaElement.addEventListener('loadedmetadata', updateDuration);
- mediaElement.addEventListener('durationchange', updateDuration);
- mediaElement.src = current.playingClip.audio;
- globalState.set('playbackTime', 0);
- }
- })
- globalState.render(['playing'], current => {
- if (current.playing && !globalState.get('playingClip')) {
- globalState.set('playing', false);
- return;
- }
- if (current.playing) {
- playPauseButton.classList.add('playing') ;
- mediaElement.play();
- } else {
- playPauseButton.classList.remove('playing') ;
- mediaElement.pause();
- }
- });
- playPauseButton.addEventListener('click', evt => {
- globalState.set('playing', !globalState.get('playing'));
- globalState.set('previewClip', globalState.get('playingClip'));
- });
- progressBar.addEventListener('click', evt => {
- if (globalState.get('playingClip')) {
- let pos = (evt.pageX - progressBar.offsetLeft) / progressBar.offsetWidth;
- globalState.set('playbackTime', pos * mediaElement.duration);
- }
- })
-
- let dragInProgress = false;
- progressBar.addEventListener('mousedown', evt => dragInProgress = true);
- progressBar.addEventListener('mouseup', evt => dragInProgress = false);
- progressBar.addEventListener('mousemove', evt => {
- if (dragInProgress && globalState.get('playingClip')) {
- let pos = (evt.pageX - progressBar.offsetLeft)
- / progressBar.offsetWidth;
- globalState.set('playbackTime', pos * mediaElement.duration);
- }
- })
- globalState.render(['playbackTime'], current => {
- if (current.playbackTime &&
- current.playbackTime != mediaElement.currentTime
- ) {
- mediaElement.currentTime = current.playbackTime;
- }
- progressBar.setAttribute('value', current.playbackTime);
- if (current.playbackTime >= progressBar.max && globalState.get('playing') == true) {
- globalState.set('playing', false);
- }
- });
- let updateTime = () => {
- let playbackTime = globalState.get('playbackTime');
- if (playbackTime != mediaElement.currentTime) {
- globalState.set('playbackTime', mediaElement.currentTime);
- }
- window.requestAnimationFrame(updateTime);
- }
- window.requestAnimationFrame(updateTime);
- } catch(exception) {
- alert("An unknown error occured.");
- console.error(exception);
- }
- </script>
- <style>
- button.play-pause svg {
- display: none;
- }
- button.play-pause svg:first-child {
- display: inline-block;
- }
- button.play-pause.playing svg {
- display: inline-block;
- }
- button.play-pause.playing svg:first-child {
- display: none;
- }
- progress.player {
- width: 100%;
- height: calc(var(--header-height) - 1em);
- vertical-align: center;
- flex-shrink: 1;
- }
- .custom-widgets progress[value] {
- /* Reset the default appearance */
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
-
- /* Get rid of default border in Firefox. */
- border: none;
- border: 1px solid var(--chrome-border);
- border-radius: var(--border-radius);
- overflow: hidden;
- }
- .custom-widgets progress[value]::-webkit-progress-bar, progress[value] {
- background: var(--input-background);
- box-shadow: inset -1px 2px calc(var(--box-shadow-spread) * 1.25) 2px rgba(255,255,255, calc(var(--highlight-strength) * 1));
- }
- /* These don't work together for some reason*/
- .custom-widgets progress[value]::-webkit-progress-value {
- background: var(--progress-bar-background);
- border-radius: var(--border-radius) 0px 0px var(--border-radius);
- }
- .custom-widgets progress[value]::-moz-progress-bar {
- background: var(--progress-bar-background);
- border-radius: var(--border-radius) 0px 0px var(--border-radius);
- }
- video.clip {
- position: absolute;
- top: 0px;
- left: 0px;
- right:0px;
- bottom: 0px;
- z-index: 1;
- width: 100%;
- height: var(--header-height);
- margin: 0px;
- opacity: 0;
- }
- </style>
- <!-- ====================================================== -->
-
- <button class="new-clip attention" title='Add new clip'><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-lg" viewBox="0 0 16 16">
- <path d="M8 0a1 1 0 0 1 1 1v6h6a1 1 0 1 1 0 2H9v6a1 1 0 1 1-2 0V9H1a1 1 0 0 1 0-2h6V1a1 1 0 0 1 1-1z"/>
- </svg><span>Clip</span></button>
- <div class="spinner hidden"></div>
- <div class="loading-message">Adding clip...</div>
- <script>
- $('button.new-clip').addEventListener('click', evt => {
- $('.modal').appendChild($('section.new-clip'));
- showModal();
- });
- </script>
- </header>
- <main>
- <section>
- <voice-graph-2d></voice-graph-2d>
- </section>
- <aside>
-
-
- <div class="current-tab"></div>
- <div class="tab-set">
- <button class="details hbox iconic"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16">
- <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
- <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
- </svg> <span>Details</span></button>
- <button class="help iconic hbox"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-question-circle" viewBox="0 0 16 16">
- <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
- <path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/>
- </svg> <span>Help</span></button>
- <button class="settings iconic">⚙ Settings</button>
- </div>
- <style>
- button.hbox {
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .tab-set button.hbox span {
- line-height: 2.5em;
- }
- .tab-set {
- position: absolute;
- bottom: 0px;
- height: calc(var(--header-height) - .5rem);
- display: flex;
- left: 0px;
- right: 0px;
- background: var(--button-background);
- }
- .wide-body .tab-set {
- right: 0px;
- }
- .tab-set button {
- transition: box-shadow .2s all;
- height: 100%;
- width: 100%;
- border-top: 1px solid var(--chrome-border);
- border-left: 1px solid var(--chrome-border);
- border-right: 0px;
- border-radius: 0px 0px var(--border-radius) var(--border-radius);
- background: var(--button-background);
- box-shadow: inset var(--box-shadow);
- }
- .current-tab {
- width: 100%;
- overflow: auto;
- height: calc(100% - 2em);
- border: 1px solid var(--chrome-border);
- border-bottom: 0px;
- border-right: 0px;
- padding: .5rem;
- padding-top: 0px;
- background: var(--chrome-background);
- transition: .3s transform;
- position: absolute;
- }
- .current-tab section {
- min-height: 100%;
- }
- .current-tab > section > *:first-child {
- height: 2em;
- line-height: 2em;
- }
- .tab-set button:disabled {
- border-top: 0px;
- opacity: 1;
- background: var(--chrome-background);
- color: var(--chrome-color);
- box-shadow: 2px 2px var(--shadow-spread) 2px rgba(0,0,0,0);
- }
- .narrow-body .current-tab.expanded {
- transform: translate(0, -60%);
- height: calc(200%);
- z-index: calc(var(--voice-graph-z-index) + 3);
- }
- #expand-collapse-button.expanded {
- transform: translate(0, -60%);
- }
- </style>
- <script>
- let tab = $('.current-tab');
- tab.addEventListener('click', evt => {
- let bb = tab.getBoundingClientRect();
- if (
- evt.clientY - bb.top < 20 && document.activeElement != $('#theme-select') &&
- evt.clientX - bb.left > (bb.right - bb.left) / 4 &&
- evt.clientX - bb.left < 3 * (bb.right - bb.left) / 4
- ) {
- if (!tab.classList.contains('expanded')) {
- tab.classList.add('expanded');
- } else {
- tab.classList.remove('expanded');
- }
- }
- }, true);
-
- for (let tab of ['settings', 'help', 'details']) {
- let tabButton = $('button.' + tab);
- tabButton.addEventListener('click', evt => {
- for (let sibling of tabButton.parentNode.querySelectorAll('button')) {
- sibling.disabled = false;
- }
- clearSelector('.current-tab > section');
- evt.target.disabled = true;
- $('aside > .current-tab').appendChild($('section.' + tab));
- });
- }
- </script>
-
- </aside>
- </main>
-
-
- <!-- ======================= Modal ======================= -->
- <style>
- .modal-shadow {
- position: absolute;
- top: 0px;
- left: 0px;
- right: 0px;
- bottom: 0px;
- background: rgba(0, 0, 0, .75);
- z-index: 3;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 10px;
- transition: .2s all;
- opacity: 1;
- }
- .modal-shadow.hidden {
- display: block;
- opacity: 0;
- pointer-events: none;
- }
- .modal {
- max-width: 600px;
- min-height: 300px;
- max-height: 90vh;
- overflow: auto;
- width: 100%;
- min-width: 50%;
- position: relative;
- background: var(--body-background);
- color: var(--body-color);
- }
- .modal > .close {
- position: absolute;
- top: 0em;
- right: .25em;
- background: none;
- color: var(--chrome-color);
- border: none;
- box-shadow: none;
- font-size: 200%;
- }
- .modal > .close:after {
- display: none;
- }
- .modal > * > *:first-child {
- margin-top: 0px;
- }
- </style>
- <div class="modal-shadow hidden">
- <div class="modal">
- <button class="close">×</button>
- </div>
- </div>
- <script>
- function showModal() {
- $('.modal-shadow').classList.remove('hidden');
- $('.modal > .close').focus();
- }
- function hideModal() {
- $('.modal-shadow').classList.add('hidden');
- $('.play-pause').focus();
- resizeEvent(null);
- }
- $('.modal > .close').addEventListener('click', evt => {
- clearSelector('.modal > section');
- hideModal();
- });
- </script>
- <!-- ===================================================== -->
-
- <!-- This is where we keep stuff we don't want visible -->
- <div id="offscreen">
-
- <style>
- .settings > label:first-child {
- font-weight: bold;
- }
- #export-clips {
- margin-right: auto;
- }
- section.settings .hbox {
- display: flex;
- align-items: center;
- }
- section.settings .hbox > * + * {
- margin-left: .25em;
- }
- #import-export {
- margin-top: .5em;
- margin-bottom: .5em;
- }
- section.settings .hbox h2 {
- margin: 0px;
- margin-right: auto;
- }
- .example {
- align-items: center;
- }
- section.settings select {
- width: 50ch;
- flex-shrink: 1;
- }
- </style>
- <section class="settings">
- <h2>Your Data</h2>
- <div id="import-export" class="hbox">
- <button id="export-clips">Export</button>
- <input type="file" id="import-clips-file"/>
- <button id="import-clips">Import</button>
- </div>
- <script>
- $('#export-clips').addEventListener('click', evt => {
- let cloneClips = JSON.parse(JSON.stringify(globalState.get('clips')));
- for (let clip of cloneClips) delete clip.marker;
- downloadFile('data:text/json;charset=utf-8,' + encodeURIComponent(
- JSON.stringify(cloneClips)
- ), 'clips.json');
- });
-
- $('#import-clips').addEventListener('click', evt => {
- if ($('#import-clips-file').files.length > 0) {
- let reader = new FileReader();
- reader.onload = evt => {
- let loadedClips = JSON.parse(evt.target.result);
- globalState.set('clips', loadedClips);
- globalState.set('playingClip', last(loadedClips));
- globalState.set('previewClip', last(loadedClips));
- }
- reader.readAsText($('#import-clips-file').files[0]);
- } else {
- alert('You need to select the file containing your exported data.');
- }
- });
- </script>
- <hr>
- <div class="hbox examples">
- <h2>Examples</h2>
- <button id="load-default-clips" onclick="loadDefaultClips()">Reload Defaults</button>
- <button id="load-extended-clips" onclick="loadExtendedClips()">Load Extended</button>
- </div>
- <p>Warning: The extended set of examples may use large amounts of data.</p>
- <hr>
- <h2>Theme</h2>
- <select id="theme-select">
- <option value="Deep Dark">Deep Dark</option>
- <option value="Light Blue">Light Blue</option>
- <option value="Light Red">Light Red</option>
- <option value="Popular">Popular</option>
- <option value="Pinkin Blue">Pinkin Blue</option>
- <option value="Pink Pank">Pink Pank</option>
- <option value="80s">80's</option>
- <option value="90s">90's</option>
- <option value="00s">00's</option>
- <option value="10s">10's</option>
- <option value="custom">Custom</option>
- <select>
- <br>
- <br>
- <details id="set-colors">
- <summary>Customize</summary>
- Rounded Corners:
- <input type="range" value="15" id="border-radius" />
- <br>
- Shadow Strength:
- <input type="range" value="5" id="box-shadow-strength" class="proportion" />
- <br>
- Shadow Spread:
- <input type="range" value="15" id="box-shadow-spread" />
- <br>
- Highlight Strength:
- <input type="range" value="0" id="highlight-strength" class="proportion" />
- <br>
- <br>
- <input type="color" id="body-background" >
- <label for="body-background">background color</label>
- <br>
- <input type="color" id="body-color" >
- <label for="body-color">text color</label>
- <br>
- <input type="color" id="chrome-color" >
- <label for="chrome-color">chrome color</label>
- <br>
- <input type="color" id="chrome-background" >
- <label for="chrome-background">chrome background</label>
- <br>
- <input type="color" id="chrome-border" >
- <label for="chrome-border">chrome border</label>
- <br>
- <input type="color" id="accent" >
- <label for="accent">accent</label>
- <br>
- <input type="color" id="accent-text" >
- <label for="accent">accent text</label>
- <br>
- <input type="color" id="button-color" >
- <label for="button-color">button color</label>
- <br>
- <input type="color" id="button-background" >
- <label for="button-background">button background</label>
- <br>
- <input type="color" id="input-color" >
- <label for="input-color">input color</label>
- <br>
- <input type="color" id="input-background" >
- <label for="input-background">input background</label>
- <br>
- </details>
- <script>
- let themeState = new StateManager();
- let themes = {
- "Light Blue": {
- "body-background":"#ffffff",
- "body-color":"#000000",
- "chrome-color":"#000000",
- "chrome-background":"#f4f7f9",
- "chrome-border":"#bfc2c4",
- "accent":"#0fa9ff",
- "accent-text":"#ffffff",
- "button-color":"#000000",
- "button-background":"#f7f7f7",
- "input-color":"#000000",
- "input-background":"#ffffff",
- "border-radius":"30",
- "box-shadow-strength":"11",
- "box-shadow-spread":"30",
- "highlight-strength":"24"
- },
- "Light Red":{"body-background":"#ffffff",
- "body-color":"#401a25",
- "chrome-color":"#401a25",
- "chrome-background":"#f9f4f5",
- "chrome-border":"#c4bfc0",
- "accent":"#ff6d96",
- "accent-text":"#ffffff",
- "button-color":"#401a25",
- "button-background":"#f7f7f7",
- "input-color":"#401a25",
- "input-background":"#ffffff",
- "border-radius":"30",
- "box-shadow-strength":"11",
- "box-shadow-spread":"30",
- "highlight-strength":"29"
- },
- "Pinkin Blue": {
- "body-background":"#c6efff",
- "body-color":"#2d2d2d",
- "chrome-color":"#2d2d2d",
- "chrome-background":"#aee8ff",
- "chrome-border":"#25a5d8",
- "accent":"#ff8cb3",
- "accent-text":"#ffffff",
- "button-color":"#000000",
- "button-background":"#55cdfc",
- "input-color":"#000000",
- "input-background":"#ffffff",
- "border-radius":"37",
- "box-shadow-strength":"10",
- "box-shadow-spread":"30",
- "highlight-strength":"50"
- },
- "Pink Pank": {"body-background":"#ffe5f8",
- "body-color":"#401a25",
- "chrome-color":"#401a25",
- "chrome-background":"#ffdcf7",
- "chrome-border":"#ea9cd9",
- "accent":"#dd55c6",
- "accent-text":"#ffffff",
- "button-color":"#000000",
- "button-background":"#ffe8f9",
- "input-color":"#401a25",
- "input-background":"#ffffff",
- "border-radius":"49",
- "box-shadow-strength":"6",
- "box-shadow-spread":"41",
- "highlight-strength":"32"
- },
- "Deep Dark": {
- "body-background":"#5e5e5e",
- "body-color":"#f2f2f2",
- "chrome-color":"#f2f2f2",
- "chrome-background":"#565656",
- "chrome-border":"#181818",
- "accent":"#169CF9",
- "accent-text":"#ffffff",
- "button-color":"#ffffff",
- "button-background":"#474747",
- "input-color":"#f2f2f2",
- "input-background":"#707070",
- "border-radius":"8",
- "box-shadow-strength":"8",
- "box-shadow-spread":"40",
- "highlight-strength":"9"
- },
- "Popular": {
- "body-background":"#ffffff",
- "body-color":"#474747",
- "chrome-color":"#474747",
- "chrome-background":"#ffffff",
- "chrome-border":"#d1d1d1",
- "accent":"#a74b91",
- "accent-text":"#ffffff",
- "button-color":"#000000",
- "button-background":"#f7f7f7",
- "input-color":"#000000",
- "input-background":"#f7f7f7",
- "border-radius":"32",
- "box-shadow-strength":"8",
- "box-shadow-spread":"50",
- "highlight-strength":"0"
- },
- "Ubuntu" : {
- "body-background": "#ffffff",
- "body-color": "#000000",
- "chrome-color": "#000000",
- "chrome-background": "#f5f6f7",
- "chrome-border": "#b4b7ba",
- "accent": "#e95421",
- "accent-text":"#ffffff",
- "button-color": "#000000",
- "button-background": "#f0f3f4",
- "input-color": "#000000",
- "input-background": "#ffffff",
- "border-radius": "0.3",
- "box-shadow-spread": "5",
- "box-shadow-strength": "0.15",
- "highlight-strength": "0.15"
- },
- "10s": {
- "body-background": "#ffffff",
- "body-color": "#2d2d2d",
- "chrome-color": "#2d2d2d",
- "chrome-background": "#f0f0f0",
- "chrome-border": "#8d8d8d",
- "accent": "#1979ca",
- "accent-text":"#ffffff",
- "button-color": "#000000",
- "button-background": "#dcdcdc",
- "input-color": "#000000",
- "input-background": "#ffffff",
- "border-radius": ".4",
- "box-shadow-spread": "5",
- "box-shadow-strength": "0.0",
- "highlight-strength": "0.0"
- },
- "90s" : {
- "body-background":"#fffee2",
- "body-color":"#2d2d2d",
- "chrome-color":"#2d2d2d",
- "chrome-background":"#c0c0c0",
- "chrome-border":"#404040",
- "accent":"#000080",
- "accent-text":"#ffffff",
- "button-color":"#000000",
- "button-background":"#c3c3c3",
- "input-color":"#000000",
- "input-background":"#ffffff",
- "border-radius":"15",
- "box-shadow-strength":"38",
- "box-shadow-spread":"0",
- "highlight-strength":"60"
- },
- "Pinkin Blues Old": {
- "body-background": "#d8f4ff",
- "body-color": "#2d2d2d",
- "chrome-color": "#2d2d2d",
- "chrome-background": "#aee8ff",
- "chrome-border": "#25a5d8",
- "accent": "#f7a8b8",
- "accent-text": "#ffffff",
- "button-color": "#000000",
- "button-background": "#55cdfc",
- "input-color": "#000000",
- "input-background": "#ffffff",
- "border-radius": ".6em",
- "box-shadow-spread": "5px",
- "box-shadow-strength": "0.15",
- "highlight-strength": "0.40",
- "border-radius":"32",
- "box-shadow-strength":"8",
- "box-shadow-spread":"5",
- "highlight-strength":"0"
- },
- "80s": {
- "body-background": "#000000",
- "body-color": "#00ff0c",
- "chrome-color": "#00ff0c",
- "chrome-background": "#000000",
- "chrome-border": "#00ff0c",
- "accent": "#00ff0c",
- "accent-text": "#000000",
- "button-color": "#00ff0c",
- "button-background": "#000000",
- "input-color": "#00ff0c",
- "input-background": "#000000",
- "border-radius": "0em",
- "box-shadow-spread": ".5",
- "box-shadow-strength": "15",
- "highlight-strength": "0.0"
- },
- "00s": {
- "body-background": "#f4f3ee",
- "body-color": "#000000",
- "chrome-color": "#000000",
- "chrome-background": "#ece9d8",
- "chrome-border": "#76a2cb",
- "accent": "#0070f6",
- "accent-text": "#ffffff",
- "button-color": "#000000",
- "button-background": "#f3f3ef",
- "input-color": "#000000",
- "input-background": "#ffffff",
- "border-radius": "12",
- "box-shadow-spread": "20",
- "box-shadow-strength": "15",
- "highlight-strength": "15"
- }
- };
- let theme = $('#theme-select').value;
- loadTheme(themes[theme]);
-
- for (let colorSelect of $$('#set-colors > input[type=color]')) {
- if (!themeState.state.hasOwnProperty(colorSelect.id)) {
- themeState.init(colorSelect.id, null);
- }
- themeState.render([colorSelect.id], current => {
- document.body.style.setProperty('--' + colorSelect.id, current[colorSelect.id]);
- });
- }
- for (let colorSelect of $$('#set-colors > input[type=color]')) {
- themeState.sync(colorSelect.id, colorSelect, 'value');
- themeState.set(colorSelect.id, colorSelect.value);
- colorSelect.addEventListener('change', evt => {
- $('#theme-select').value = 'custom';
- });
- }
- for (let select of $$('#set-colors > input[type=range]')) {
- themeState.sync(select.id, select, 'value');
- select.addEventListener('change', evt => {
- $('#theme-select').value = 'custom';
- });
- }
- for (let select of $$('#set-colors > input[type=range]')) {
- themeState.render([select.id], current => {
- if (select.classList.contains('proportion')) {
- document.body.style.setProperty('--' + select.id, current[select.id] / 100);
- }
- if (select.id == 'border-radius') {
- document.body.style.setProperty('--' + select.id, (current[select.id] / 50) + 'em');
- }
- if (select.id == 'box-shadow-spread') {
- document.body.style.setProperty('--' + select.id, (current[select.id] / 100) * 10 + 'px');
- }
- });
- }
- function doLoadTheme(theme) {
- if (themes[theme]) {
- loadTheme(themes[theme]);
- } else {
- setTimeout(() => doLoadTheme(theme), 1000);
- }
- }
- $('#theme-select').addEventListener('change', evt => {
- let theme = $('#theme-select').value;
- doLoadTheme(theme);
- });
- for (let colorSelect of $$('#set-colors > input[type=color]')) {
- if (!themeState.state.hasOwnProperty(colorSelect.id)) {
- themeState.init(colorSelect.id, current[colorSelect.id]);
- }
- themeState.render([colorSelect.id], current => {
- document.body.style.setProperty('--' + colorSelect.id, current[colorSelect.id]);
- });
- }
- theme = $('#theme-select').value;
- loadTheme(themes[theme]);
- function nativeWidgets() {$('body').classList.remove('custom-widgets')};
- function customWidgets() {$('body').classList.add('custom-widgets')};
- function recordTheme() {
- let theme = {};
- for (let key in themeState.state) {
- theme[key] = themeState.get(key);
- }
- alert(JSON.stringify(theme));
- }
- function loadTheme(theme) {
- for (let key in theme) {
- if (themeState.state.hasOwnProperty(key)) {
- themeState.set(key, theme[key]);
- } else {
- themeState.init(key, theme[key]);
- }
- }
- }
- function nativeStyle() {
- whiteLight();
- document.body.style.setProperty('--body-background', 'White');
- document.body.style.setProperty('--body-color', 'Black');
- document.body.style.setProperty('--chrome-color', 'WindowText');
- document.body.style.setProperty('--chrome-background', 'Window');
- document.body.style.setProperty('--chrome-border', 'ButtonShadow');
- document.body.style.setProperty('--accent', 'Highlight');
- document.body.style.setProperty('--button-color', 'ButtonText');
- document.body.style.setProperty('--button-background', 'ButtonFace');
- document.body.style.setProperty('--input-color', '#000000');
- document.body.style.setProperty('--border-radius', 15 / 50 + "em");
- document.body.style.setProperty('--box-shadow-spread', .50 * 10 + "px");
- document.body.style.setProperty('--box-shadow-strength', '0.05');
- document.body.style.setProperty('--highlight-strength', '0');
- document.querySelector('input.highlight-strength').value = '0';
- nativeWidgets();
- }
- </script>
- </section>
- <style>
- .wide-body .mobile-help {
- display: none;
- }
- .mobile-help {
- font-size: smaller;
- font-style: italic;
- }
- .wide-body .mobile-help + p {
- margin-top: 0px;
- }
- .help details + details {
- margin-top: .5em;
- }
- .help .notice {
- text-align: center;
- margin-bottom: 0px;
- font-weight: bold;
- }
- .notice + h2 {
- margin-top: 0px;
- }
- </style>
- <section class="help">
- <!--p class='notice'>Help us out! Answer
- <a href="https://brown.co1.qualtrics.com/jfe/form/SV_4PgW7EKNvnxnWLk">our survey</a>.
- </p-->
- <h2>Tutorial</h2>
- <div class='mobile-help'>
- Mobile users: You can tap the top edge of this pane
- to expand/collapse it for easier reading.
- </div>
- <p>
- <img src="resources/logo.png" alt="An acoustic guitar where the headstick is semi-triangular forming a Mars-sign along with the neck and sound hold. Likewise, the sound hole, strings, and bridge form a Venus sign. The image is in the pink, blue, and white transgender pride colors." style="float: right" width='128'/>
- This app visualizes the pitch and resonance of voices in recordings over time.
- <strong>Pitch</strong> is our perception of the frequency at which the vocal folds vibrate.
- <strong>Resonance</strong> is the effect exerted on a sound by the passage the air travels through.
- The program plots recordings in a <strong>2-dimensional space</strong>,
- where the Y-axis represents pitch,
- and the X-axis represents resonance,
- with brighter sounds represented with a higher percentage.
- </p>
-
- <p>Typically, voices perceived as female have brighter resonance and higher pitch (top right),
- while those perceived as male have darker resonance and lower pitch (bottom left).
- These features can be blended in different degrees
- to create a <strong>broad spectrum</strong> of perceptual and aesthetic qualities related to gender.
- There are no sharp cutoffs at which a voice is
- guaranteed to be gendered a specific way,
- just as the colors in the graph blend seamlessly into one another.
- <p>Press the <strong>play button</strong> in the top-left corner of the app to play the clip
- associated with the selected point on the graph.
- Examine some of the provided examples to get a sense
- of how positions on the graph correspond to your perceptions of gender.
- (Note: Example clips may take some time to show up on slow connections).
- You can visualize your own recordings by pressing the <strong>add clip button</strong>
- in the top-right and submitting a recording.
- It will take some time to process,
- so you may wish to examine the other clips while the recording is processed.
- <p>Your clips are sent to our server for processing,
- but they are <strong>not stored persistently</strong>.
- You can save the sound file from your recording
- using the <strong>download button</strong> in the "Details" tab.
- From the settings tab,
- you can also <strong>export</strong> all of your data in a single file
- which can be imported for viewing at another time.
- Otherwise, your data will be lost when you exit the page.
- <p>We hope that using this program will help you understand how voices are gendered
- and assess your own progress if you are trying to alter your voice
- to better match your gender identity or desired presentation. Good luck!
- <h2>FAQ</h2>
- <details>
- <summary>Why does the marker move around so much even when I'm not changing my voice drastically?</summary>
- <p>Most speech has a broad range of values in pitch and resonance,
- e.g. The highest and brightest phone spoken in a sample of typical male speech
- may well be similar in pitch in resonance to the <em>average</em> phone in typical female speech.
- Our perception of a voice is based on the overall tendencies within speech
- rather than the moment-to-moment features of individual phones.</p>
- </details>
- <details>
- <summary>Why do all the markers start so close together?</summary>
- <p>When not playing, the markers are displayed at the median values across the whole clip.
- Because there's a wide range of values in most speech,
- this pushes the median towards the center.
- However, a small change in median values
- can have a big influence on how a voice is gendered</p>
- </details>
- <details>
- <summary>Does this program support languages other than English?</summary>
- <p>Not currently.
- If you add recording in another language,
- you <em>might</em> get an output,
- but it will probably not be very accurate.</p>
- </details>
- <details>
- <summary>Do accent and dialect affect pitch and resonance?</summary>
- <p>Yes, so you may wish to upload recordings of other speakers with your accent or dialect
- to better compare with your own voice.</p>
- </details>
- <details>
- <summary>Ok, I can see my resonance now, but how do I actually change it?</summary>
- <p>There are several ways. One is to raise (brightening) or lower (darkening) your larynx.
- Your larynx raises when you swallow and lowers when you yawn.
- You may be able to feel it move if you put your hand over the front of your neck
- while you make one of these movements.
- If you can learn to control your larynx position in normal speech,
- it can have a big effect on your resonance.
- </p>
- <p>
- Your resonance is also affected by the space in your mouth.
- Holding your tongue higher in your mouth as you speak will brighten your resonance,
- and holding it lower will darken it.
- </p>
- </details>
- <details>
- <summary>Even though my pitch and resonance seem to be in the right spot,
- I don't feel like my voice sounds like I want it to.</summary>
- <p>There are many dimensions to voice other than pitch and resonance,
- so you might need to target other features.
- You might also be straining to reach a particular point,
- which can be audible to a listener.
- It might help to pick a less extreme target
- which you can reach comfortably.</p>
- </details>
- <details>
- <summary>Can I donate to encourage further development?</summary>
- <p>Yes! You can donate through any of the following platforms:</p>
- <ul>
- <li><a href="https://liberapay.com/lmcnulty">Liberapay</a> - Recurring donations, no extra fees, free-software focus
- <li><a href="https://ko-fi.com/lmcnulty">Ko-fi</a> - One-time or recurring donations, no extra fees
- <li><a href="https://www.patreon.com/lmcnulty">Patreon</a> - Recurring donations, takes a cut
- </ul>
- </details>
- <h2>Credits</h2>
- <p>This application is developed by <a href="https://lmcnulty.me">Luna McNulty</a>
- as part of a research project for her Sc. M. program at Brown University.
- Its content is available under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a>
- and uses Creative-Commons-licensed sound files from
- Morgan and Christine Lemmer-Webber's podcast <a href="https://fossandcrafts.org">Foss and Crafts</a>
- as well as public domain content audiobook content from <a href="librivox.org">Librivox</a>.</p>
- </section>
-
- <!-- Details -->
- <style>
- .details h2 {
- margin-bottom: .5em;
- margin-top: 0px;
- }
- .details {
- position: relative;
- }
- .details table {
- margin-bottom: .5em;
- margin-top: .5em;
- border-collapse: collapse;
- margin: auto;
- }
- .details table td, .details table th {
- border: 1px solid var(--chrome-border);
- padding: .25em;
- text-align: center;
- }
- .details .view {
- display: none;
- }
- .details.has-selection .view {
- display: block;
- }
- .details.has-selection .instructions{
- display: none;
- }
- .details .hbox h2 {
- display: inline;
- margin: 0px;
- margin-right: auto;
- }
- .details .hbox > * + * {
- margin-left: .5em;
- }
- .details .hbox {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- margin-bottom: 1em;
- height: 2em;
- }
- .details label {
- font-weight: bold;
- }
- td.highlight {
- background: var(--accent);
- color: white;
- color: var(--accent-text);
- }
- .details details {
- margin-top: .5em;
- }
- .details summary {
- margin-bottom: .5em;
- }
- #delete-confirm.hidden {
- display: inline-block;
- overflow: hidden;
- opacity: 0;
- width: 1ch;
- height: 1ch;
- visibility: hidden;
- }
- #delete-confirm {
- transition: .3s all;
- }
- </style>
- <section class="details">
- <h2 class='instructions'>Clip Details</h2>
- <div class='instructions'>
- When you select a clip/point from the graph it will be displayed here.
- If you just loaded the page and are seeing this,
- your browser is downloading the examples
- and they will display as they arrive.
- If you are on a slow connection,
- you may wish to start by reading the instructions in the help tab
- while you wait.
- </div>
- <div class='view'>
- <div class="hbox">
- <h2 id="preview-title"></h2>
- <span class='buttons'>
- <span id='delete-confirm' class='hidden'>Press again to delete</span>
- <button class='delete' title='Delete Clip'><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
- <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
- <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
- </svg></button>
- <button class='download' title='Download Clip'><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-download" viewBox="0 0 16 16">
- <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
- <path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
- </svg></button>
- </span>
- </div>
- <table>
- <tr>
- <th></th>
- <th>Mean</th>
- <th>Median</th>
- <th>St. dev.</th>
- </tr>
- <tr>
- <th>Pitch</th>
- <td id='preview-mean-pitch'>--</td>
- <td id='preview-median-pitch'>--</td>
- <td class='highlight'><span id='preview-stdev-pitch'></span>*</td>
- </tr>
- <tr>
- <th>Resonance</th>
- <td id='preview-mean-resonance'>--</td>
- <td id='preview-median-resonance'>--</td>
- <td id='preview-stdev-resonance'>--</td>
- </tr>
- </table>
- <br>
- <div>
- <label for="preview-transcript">Transcript</label>:
- <span id='preview-transcript'></span>
- </div>
- <details>
- <summary>Advanced</summary>
- <button id='dl-json'>Download JSON</button>
- </details>
- <hr>
- <div>
- <small>* In many contexts, greater variation in pitch (standard deviation) is
- associated with women's speech, whereas monotone is associated with men's speech.
- </small>
- </div>
- </div>
- </section>
- <script>
- globalState.render(['previewClip'], current => {
- if (current.previewClip) {
- $('.details').classList.add('has-selection');
- $('#preview-title').innerHTML = current.previewClip.title || '';
- if (current.previewClip.meanPitch) {
- $('#preview-mean-pitch').innerHTML = Math.round(current.previewClip.meanPitch) + 'Hz';
- $('#preview-mean-resonance').innerHTML = Math.round(current.previewClip.meanResonance * 100) + '%';
- $('#preview-stdev-pitch').innerHTML = Math.round(current.previewClip.stdevPitch) + 'Hz';
- $('#preview-stdev-resonance').innerHTML = Math.round(current.previewClip.stdevResonance * 100) + '%';
- $('#preview-median-pitch').innerHTML = Math.round(current.previewClip.medianPitch) + 'Hz';
- $('#preview-median-resonance').innerHTML = Math.round(current.previewClip.medianResonance * 100) + '%';
- }
- if (current.previewClip.transcript) {
- $('#preview-transcript').innerHTML = current.previewClip.transcript;
- }
- } else {
- $('.details').classList.remove('has-selection');
- }
- })
- $('section.details button.download').addEventListener('click', evt => {
- let previewClip = globalState.get('previewClip');
- downloadFile(previewClip.audio, previewClip.title);
- });
- let lastDeletePress = 0;
- $('section.details button.delete').addEventListener('click', evt => {
- if (Date.now() - lastDeletePress > 4000) {
- $('#delete-confirm').classList.remove('hidden');
- lastDeletePress = Date.now();
- window.setTimeout(() => {
- $('#delete-confirm').classList.add('hidden');
- }, 4000)
- } else {
- let previewClip = globalState.get('previewClip');
- globalState.mutate('clips', clips => {
- let index = clips.indexOf(previewClip);
- clips.splice(index, 1);
- })
- globalState.set('previewClip', null);
- }
- });
- $('#dl-json').addEventListener('click', evt => {
- let previewClip = globalState.get('previewClip');
- let cloneClip = JSON.parse(JSON.stringify(previewClip));
- delete cloneClip.marker;
- downloadFile(
- 'data:text/json;charset=utf-8,'
- + encodeURIComponent(JSON.stringify(cloneClip)),
- previewClip.title + '.json'
- );
- })
- </script>
-
- <section class="new-clip">
- <style>
- #record-voice + label + div, #upload-file + label + div {
- margin-left: 1em;
- padding: .5em;
- }
- #script-select {
- max-width: calc(100% - 6ch);
- }
- #title-input {
- max-width: calc(100% - 16ch);
- }
- section.new-clip .new-clip {
- margin-bottom: 1em;
- }
- .new-clip .hbox {
- display: flex;
- align-items: center;
- }
- .new-clip .hbox select, .hbox input[type=text] {
- width: 100%;
- flex-shrink: 1;
- }
- .new-clip .hbox label {
- margin-left: 1em;
- margin-right: .5em;
- }
- .new-clip .hbox label:first-child {
- margin-left: 0px;
- }
- .new-clip h2 {
- margin-top: 0px;
- }
- .new-clip #submit {
- float: right;
- }
- #upload-file + label + div, #record-voice + label + div {
- }
- /* Show upload/recording elements only when that option is selected. */
- #record-voice + label + div { display: none; }
- #record-voice:checked + label + div { display: block; }
- #upload-file + label + div { display: none;}
- #upload-file:checked + label + div { display: block;}
- </style>
- <h2>Add a Clip</h2>
- <div class="hbox">
- <label for="title-input">Title</label>
- <input type="text" id="title-input" value="Untitled"/>
- <label for="color-select">Color</label>
- <input type="color" id="color-select" value="#000000"/>
- </div>
- <br>
- <!-- Script selection -->
- <div class="hbox">
- <label for="script-select">Script </label>
- <select class="script" id="script-select">
-
- <option value="When the sunlight strikes raindrops in the air, they act as a prism and form a rainbow. The rainbow is a division of white light into many beautiful colors. These take the shape of a long round arch, with its path high above, and its two ends apparently beyond the horizon. There is , according to legend, a boiling pot of gold at one end. People look, but no one ever finds it."
- >Rainbow Passage</option>
- <option value="You wish to know all about my grandfather. Well, he is nearly 93 years old, yet he still thinks as swiftly as ever. He dresses himself in an old black frock coat, usually several buttons missing. A long beard clings to his chin, giving those who observe him a pronounced feeling of the utmost respect. When he speaks, his voice is just a bit cracked and quivers a bit. Twice each day he plays skillfully and with zest upon a small organ. Except in the winter when the snow or ice prevents, he slowly takes a short walk in the open air each day. We have often urged him to walk more and smoke less, but he always answers, “Banana oil!” Grandfather likes to be modern in his language.">
- Grandfather passage</option>
- <option value="Please call Stella. Ask her to bring these things with her from the store: Six spoons of fresh snow peas, five thick slabs of blue cheese, and maybe a snack for her brother Bob. We also need a small plastic snake and a big toy frog for the kids. She can scoop these things into three red bags, and we will go meet her Wednesday at the train station."
- >Please call stella</option>
- <option value="
- That quick beige fox jumps in the air over each thin dog. Look out, I shout, for he's foiled you again, creating chaos."
- >Phonetic Pangram: Quick beige fox...</option>
-
- <option value="
- The beige hue on the waters of the loch impressed all, including the French queen, before she heard that symphony again, just as young Arthur wanted."
- >Phonetic Pangram: Waters of the loch</option>
-
- <option value="
- I am afraid to own a Body—
- I am afraid to own a Soul—
- Profound—precarious Property—
- Possession, not optional—
-
- Double Estate—entailed at pleasure
- Upon an unsuspecting Heir—
- Duke in a moment of Deathlessness
- And God, for a Frontier."
- >Emily Dickinson: Afraid to own a body</option>
-
- <option value="
- Rearrange a ''Wife's'' affection!
- When they dislocate my Brain!
- Amputate my freckled Bosom!
- Make me bearded like a man!
- Blush, my spirit, in thy Fastness —
- Blush, my unacknowledged clay —
- Seven years of troth have taught thee
- More than Wifehood every may!"
- >Emily Dickinson: Rearrange a "Wife's" affection</option>
-
- <option value="
- Yet be thou mindful ever, —
- Heat from fire, fire from heat —
- None can see sever, none can sever "
- >Heat from fire</option>
- <option value="
- The moon is in doubt
- over whether to be
- a man or a woman.
- There've been rumors,
- all manner of allegations,
- bold claims and public lies:
- He's belligerent. She's in a funk.
- When he fades, the world teeters.
- When she burgeons, crime blossoms.
- O how the operatic impulse wavers!
- Dip deep, my darling, into the blank pool."
- >Rita Dove: Trans-</option>
-
-
- <option value="
- Oh, but had the artisan who made me
- created me instead — a fair woman.
- Today I would be wise and insightful.
- We would weave, my friends and I,
- and in the moonlight spin our yarn,
- and tell our stories to one another,
- from dusk till midnight."
- >Kalonymus: Even Bochan</option>
-
- <option value="
- From childhood's hour I have not been
- As others were — I have not seen
- As others saw — I could not bring
- My passions from a common spring —
- From the same source I have not taken
- My sorrow — I could not awaken
- My heart to joy at the same tone —
- And all I lov'd — I lov'd alone."
- >Edgar Allan Poe: Alone</option>
-
- <option value="custom">Custom</option>
- }
- </select>
- </div>
- <textarea rows="6" id="transcript"></textarea>
- <script>
- globalState.render(['transcript'], current => {
- $('#transcript').value = current.transcript;
- });
- function formatTranscript(value) {
- return value.replace(/\s\s+/g, '\n').trim()
- }
- globalState.set('transcript', formatTranscript($('#script-select').value));
- $('#script-select').addEventListener('change', evt => {
- let value = $('#script-select').value;
- globalState.set('transcript', value == 'custom' ? '' : formatTranscript(value));
- });
- $('#transcript').addEventListener('change', evt => {
- if (globalState.get('transcript') != $('#transcript').value) {
- globalState.set('transcript', $('#transcript').value);
- $('#script-select').value = 'custom';
- }
- });
- </script>
- <!-- Upload Options -->
- <input type="radio" name="add-recording" id="upload-file" checked>
- <label for="upload-file">Upload a file</label>
- <div>
- <input type='file' id='audio-file-input' name='recording'
- capture="user" accept="audio/*,video/*,audio/mpeg,audio/mp4,audio/x-aiff,audio/ogg,audio/vorbis,audio/opus,audio/vnd.wav,.wav,.mp3,.ogg,.opus,.m4a,.webm"/>
- </div>
- <br>
- <input type="radio" name="add-recording" id="record-voice">
- <label for="record-voice">Record your voice</label>
- <div>
- <button id="start-recording">⏺︎ Start</button>
- <button id="stop-recording">⏹︎ Stop</button>
- Recorded <span id="recording-count">0</span> seconds
- <br>
- <br>
- <audio controls id="recording-preview"></audio>
- </div>
- <!-- Submit -->
- <button id="submit" class='attention' onclick="submitAudio()">Add Clip</button>
- <script>
- try {
- // record from their microphone in-browser,
- let mediaRecorder = null;
- let audioChunks = [];
- let recordedAudio = null;
- function recordAudio() {
- let stopButton = document.querySelector("#stop-recording");
- let startButton = document.querySelector("#start-recording");
- const startRecording = () => {
- $('#recording-count').innerHTML = '0';
- let recordingInterval = setInterval(() => {
- $('#recording-count').innerHTML = Number($('#recording-count').innerHTML) + 1;
- }, 1000);
- mediaRecorder.addEventListener("stop", () => {
- clearInterval(recordingInterval);
- });
- stopButton.disabled = false;
- startButton.disabled = true;
- audioChunks = [];
- mediaRecorder.start();
- mediaRecorder.addEventListener("dataavailable", event => {
- audioChunks.push(event.data);
- });
- }
- function alertRecordingFailure(exception) {
- alert("Sorry, something went wrong. "
- + "Your device's browser may not be able to record audio. "
- + "Try uploading a file recorded from a native app instead "
- + "or using a different browser."
- );
- console.error(exception);
- }
- if (mediaRecorder) { startRecording(); } else {
- try {
- navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
-
- mediaRecorder = new MediaRecorder(stream);
-
- mediaRecorder.addEventListener("stop", () => {
- stopButton.disabled = true;
- startButton.disabled = false;
- const audioBlob = new Blob(audioChunks);
- recordedAudio = audioBlob;
- let reader = new FileReader(recordedAudio);
- reader.readAsDataURL(recordedAudio);
- reader.addEventListener("load", () => {
- $('#recording-preview').src = reader.result.replace(
- /data:.*;base64/, "data:audio/wav;base64"
- );
- $('#recording-preview').load();
- });
- });
- stopButton.addEventListener("click", () => {mediaRecorder.stop();}, false);
- startRecording();
- }).catch(exception => alertRecordingFailure(exception));
- } catch(exception) { alertRecordingFailure(exception); }
- }
- }
- $('#start-recording').addEventListener('click', evt => {
- recordAudio();
- });
- function submitAudio() {
- let clip = new Clip();
- let formData = new FormData();
- let transcriptText = $('#transcript').value;
- formData.append('transcript', transcriptText);
- formData.append('referrer', document.referrer);
- formData.append('screen-width', window.screen.width);
- formData.append('screen-height', window.screen.height);
- formData.append('lang', navigator.language || navigator.userLanguage);
- clip.transcript = transcriptText;
- clip.title = $('#title-input').value;
- clip.color = $('#color-select').value;
- if ($('#upload-file').checked) {
- fileInput = $('#audio-file-input')
- if (fileInput.files.length < 1) return
- let recording = fileInput.files[0];
- formData.append('recording', recording);
- clip.loadAudioFile(recording);
- } else if ($('#record-voice').checked) {
- formData.append('recording', recordedAudio);
- clip.loadAudioFile(recordedAudio);
- } else {
- alert('Please upload a file or make a recording.');
- return;
- }
- $('button.new-clip').classList.add('hidden');
- $('button.new-clip + .spinner').classList.remove('hidden');
-
- hideModal();
-
- fetch('./backend.cgi', { method: 'POST', body: formData})
- .then(response => {
- if (!response.ok) throw new Error(response.status);
- if (response.headers.get('Content-Length') < 2) return {};
- return response.json();
- }).then(data => {
- $('button.new-clip').classList.remove('hidden');
- $('button.new-clip + .spinner').classList.add('hidden');
- console.assert(data.hasOwnProperty('phones') && data.phones.length < 1);
-
- clip.loadResponse(data);
- globalState.mutate('clips', clips => clips.push(clip));
- globalState.set('playing', false);
- globalState.set('playingClip', clip);
- globalState.set('previewClip', clip);
- }).catch(() => {
- alert(
- "Sorry, we weren't able to process your recording. " +
- "Make sure to speak clearly and ensure " +
- "that your transcript matches the audio. " +
- "For increased chance of success," +
- "try using short clips with a 1-second period of silence."
- );
- $('button.new-clip').classList.remove('hidden');
- $('button.new-clip + .spinner').classList.add('hidden');
- });
- }
- } catch (exception) {
- alert("An unknown error occured.");
- console.error(exception);
- }
- </script>
- </section>
-
- </div>
-
- let darkIcon = String(`
- <svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- id="svg5181"
- version="1.1"
- viewBox="0 0 8.5024996 8.5054264"
- height="32.146492"
- width="32.135433">
- <defs
- id="defs5175" />
- <metadata
- id="metadata5178">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- transform="translate(-24.474941,-137.0211)"
- id="layer1">
- <rect style="fill:none;fill-opacity:1;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0"
- id="rect4546-3"
- width="7.9375"
- height="7.9375"
- x="24.757441"
- y="137.30507" />
- <path
- id="path4571"
- d="m 27.40326,137.30506 c -3.43959,1.32291 -3.43959,6.61458 0,7.9375"
- style="fill:none;stroke:currentColor;stroke-width:0.565;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
- <path
- style="fill:none;stroke:currentColor;stroke-width:0.565;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 27.93242,138.09881 c -2.64583,1.85208 -2.64583,4.49791 0,6.35"
- id="path4573" />
- <path
- style="fill:none;stroke:currentColor;stroke-width:0.565;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 30.0491,137.30506 c 3.43959,1.32291 3.43959,6.61458 0,7.9375"
- id="path5740" />
- <path
- id="path5742"
- d="m 29.51994,138.09881 c 2.64583,1.85208 2.64583,4.49791 0,6.35"
- style="fill:none;stroke:currentColor;stroke-width:0.565;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
- </g>
- </svg>
- `)
- let brightIcon = String(`
- <svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="32.135433"
- height="32.153236"
- viewBox="0 0 8.5025005 8.5072107"
- version="1.1"
- id="svg4565"
- inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
- sodipodi:docname="bright.svg">
- <defs id="defs4559">
- <link href="style.css" type="text/css" rel="stylesheet"
- xmlns="http://www.w3.org/1999/xhtml"/>
- </defs>
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="11.2"
- inkscape:cx="12.379905"
- inkscape:cy="11.113832"
- inkscape:document-units="mm"
- inkscape:current-layer="svg4565"
- showgrid="false"
- fit-margin-top="0"
- fit-margin-left="0"
- fit-margin-right="0"
- fit-margin-bottom="0"
- units="px"
- inkscape:window-width="1920"
- inkscape:window-height="1002"
- inkscape:window-x="0"
- inkscape:window-y="0"
- inkscape:window-maximized="1" />
- <metadata
- id="metadata4562">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(-32.790418,-144.57973)">
- <rect
- style="fill:none;fill-opacity:1;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0"
- id="rect4581"
- width="7.9375"
- height="7.9375"
- x="33.072918"
- y="144.86459"
- inkscape:export-xdpi="191.19"
- inkscape:export-ydpi="191.19" />
- <path
- sodipodi:nodetypes="cc"
- inkscape:connector-curvature="0"
- id="path4583"
- d="m 33.866667,144.86458 c 1.85208,1.32292 1.85208,6.61459 0,7.9375"
- style="fill:none;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
- <path
- style="fill:none;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 35.454167,145.39375 c 1.32291,1.85209 1.32291,5.02709 0,6.87917"
- id="path4585"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc" />
- <path
- style="fill:none;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="m 40.216657,144.86459 c -1.85208,1.32292 -1.85208,6.61459 0,7.9375"
- id="path833"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc" />
- <path
- sodipodi:nodetypes="cc"
- inkscape:connector-curvature="0"
- id="path835"
- d="m 38.629157,145.39376 c -1.32291,1.85209 -1.32291,5.02709 0,6.87917"
- style="fill:none;stroke:currentColor;stroke-width:0.56500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
- </g>
- </svg>
- `)
-
- <script src="./ui/index.js"></script>
- <script src="./ui/voice-graph.js"></script>
-
- </body>
- </html>
|