Created
June 8, 2024 01:00
-
-
Save mattdanielbrown/5e834651c72e71638021bf6a669c6af0 to your computer and use it in GitHub Desktop.
Revisions
-
mattdanielbrown created this gist
Jun 8, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,76 @@ <menu> <h1></h1> </menu> <main> <ul class="words"> <div class="slider"> <li class="word"> <div class="position"> <input class="string" type="text" value="daisy"> </div> <div class="settings"> <button class="play"></button> <button class="delete"></button> <label>Rate</label> <input class="rate" type="range" min="0" value="5" max="50"> <label>Pitch</label> <input class="pitch" type="range" min="0" value="150" max="200"> </div> </li><li class="word"> <div class="position"> <input class="string" type="text" value="daisy"> </div> <div class="settings"> <button class="play"></button> <button class="delete"></button> <label>Rate</label> <input class="rate" type="range" min="0" value="5" max="50"> <label>Pitch</label> <input class="pitch" type="range" min="0" value="125" max="200"> </div> </li><li class="word"> <div class="position"> <input class="string" type="text" value="give me your"> </div> <div class="settings"> <button class="play"></button> <button class="delete"></button> <label>Rate</label> <input class="rate" type="range" min="0" value="5" max="50"> <label>Pitch</label> <input class="pitch" type="range" min="0" value="100" max="200"> </div> </li><li class="word"> <div class="position"> <input class="string" type="text" value="answer"> </div> <div class="settings"> <button class="play"></button> <button class="delete"></button> <label>Rate</label> <input class="rate" type="range" min="0" value="5" max="50"> <label>Pitch</label> <input class="pitch" type="range" min="0" value="75" max="200"> </div> </li><li class="word"> <div class="position"> <input class="string" type="text" value="do"> </div> <div class="settings"> <button class="play"></button> <button class="delete"></button> <label>Rate</label> <input class="rate" type="range" min="0" value="5" max="50"> <label>Pitch</label> <input class="pitch" type="range" min="0" value="100" max="200"> </div> </li> </div> </ul> <button class="plus-sign add-words"></button> </main> <nav> <button class="play-state"></button> <select class="voice-name"></select><i></i> <button class="export">Export</button> <button class="import">Import</button> </nav> <aside> <div class="center"> <p class="exporting">Copy and save this code.</p> <p class="importing">Paste your code into the text area.</p> <button class="close"></button> <textarea></textarea> </div> </aside> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,229 @@ class SubClass constructor: ( parent , data ) -> @.root = parent.root or parent @.parent = parent @.init? data class Words extends SubClass template: " <div class=\"position\"> <input class=\"string\" type=\"text\" /> </div> <div class=\"settings\"> <button class=\"play\"></button><button class=\"delete\"></button> <label>Rate</label> <input class=\"rate\" type=\"range\" min=\"0\" value=\"10\" max=\"50\" /> <label>Pitch</label> <input class=\"pitch\" type=\"range\" min=\"0\" value=\"100\" max=\"200\" /> </div> " list: [] elements: [] init: -> @.getElements() @.addListeners() getElements: -> @.sliderContainer = document.querySelector ".words" @.elementContainer = document.querySelector ".words .slider" @.elements = document.querySelectorAll ".word" @.newWordButton = document.querySelector ".add-words" for element in @.elements @.addItemListeners element addListeners: -> @.newWordButton.addEventListener "click" , @.makeNewWordElement @.elementContainer.addEventListener "mousewheel" , @.onScroll onScroll: ( e ) => e.preventDefault() @.sliderContainer.scrollLeft += e.deltaY @.sliderContainer.scrollLeft += e.deltaX makeNewWordElement: => item = document.createElement "li" item.setAttribute "class" , "word" item.innerHTML = @.template last = @.elementContainer.lastChild @.elementContainer.insertBefore item , last @.addItemListeners item item.querySelector(".string").focus() addItemListeners: ( item ) -> item.querySelector( ".delete" ).onclick = @.deleteItem item.querySelector( ".play" ).onclick = => @.root.parse.item item item.querySelector( ".string" ).onchange = => @.root.parse.item item item.querySelector( ".pitch" ).onchange = => pitch = item.querySelector( ".pitch" ).value / 100 item.querySelector( ".string" ).style.top = "#{100 - (10+ ( pitch / 2 * 80 ))}%" @.root.parse.item item item.querySelector( ".rate" ).onchange = => @.root.parse.item item pitch = item.querySelector( ".pitch" ).value / 100 item.querySelector( ".string" ).style.top = "#{100 - (10+ ( pitch / 2 * 80 ))}%" deleteItem: ( event ) => item = event.srcElement.parentNode.parentNode item.parentNode.removeChild item class Parse extends SubClass voice: null utterances: [] init: -> voices = window.speechSynthesis.getVoices() select = document.querySelector ".voice-name" if voices.length is 0 setTimeout => @.init() , 100 else for voice in voices if voice.name.substring( 0, 6 ) isnt "Google" option = document.createElement "option" option.text = voice.name option.voice = voice select.appendChild option item: ( item ) => @.utterances = [] voices = speechSynthesis.getVoices() name = document.querySelector ".voice-name" voice = name[ name.selectedIndex ].voice string = item.querySelector( ".string" ).value rate = item.querySelector( ".rate" ).value / 10 pitch = item.querySelector( ".pitch" ).value / 100 if string.length > 0 utterance = new SpeechSynthesisUtterance string utterance.voice = voice utterance.pitch = pitch utterance.rate = rate utterance.element = item @.utterances.push utterance @.root.player.run() words: => @.utterances = [] voices = speechSynthesis.getVoices() items = document.querySelectorAll ".words .slider .word" name = document.querySelector ".voice-name" voice = name[ name.selectedIndex ].voice for item in items string = item.querySelector( ".string" ).value rate = item.querySelector( ".rate" ).value / 10 pitch = item.querySelector( ".pitch" ).value / 100 item.querySelector( ".string" ).style.top = "#{100 - (10 + ( pitch / 2 * 80 ))}%" if string.length > 0 utterance = new SpeechSynthesisUtterance string utterance.voice = voice utterance.pitch = pitch utterance.rate = rate utterance.element = item @.utterances.push utterance @.root.player.run() class Player extends SubClass run: -> utterances = @.root.parse.utterances if utterances.length > 0 for utterance, index in utterances if index + 1 isnt utterances.length utterance.next = utterances[ index + 1 ] self = @ utterance.onend = -> @.element.classList.remove "playing" next = @.next self.speak next @.onend = undefined @.speak utterances[0] speak: ( utterance ) -> @.lastUtterance?.element.classList.remove "playing" @.lastUtterance = utterance @.lastUtterance.element.classList.add "playing" if @.lastUtterance.onend is null @.lastUtterance.onend = -> @.element.classList.remove "playing" window.speechSynthesis.speak utterance class Interface extends SubClass init: -> @.getElements() @.addListeners() getElements: -> @.playingButton = document.querySelector ".play-state" addListeners: -> @.playingButton.addEventListener "click" , => if @.playingButton.classList.contains "playing" # todo: pause else @.root.parse.words() setInterval => if window.speechSynthesis.speaking @.playingButton.classList.add "playing" else @.playingButton.classList.remove "playing" , 100 class Porting extends SubClass init: -> @.getElements() @.addListeners() getElements: -> @.importButton = document.querySelector "button.import" @.exportButton = document.querySelector "button.export" @.modal = document.querySelector "aside" @.closeButton = @.modal.querySelector ".close" addListeners: -> @.importButton.addEventListener "click" , => @.modal.classList.add "active" @.modal.classList.remove "exporting" @.modal.classList.add "importing" @.exportButton.addEventListener "click" , => @.modal.classList.add "active" @.modal.classList.remove "importing" @.modal.classList.add "exporting" @.closeButton.addEventListener "click" , => @.modal.classList.remove "active" class App constructor: -> @.words = new Words @ @.parse = new Parse @ @.player = new Player @ @.interface = new Interface @ @.porting = new Porting @ new App
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,8 @@ Singing Text To Speech ---------------------- Still a WIP but it's fun to play with. Only tested in chrome. A [Pen](https://codepen.io/mattdanielbrown/pen/LYojZqB) by [Matt Daniel Brown](https://codepen.io/mattdanielbrown) on [CodePen](https://codepen.io). [License](https://codepen.io/license/pen/LYojZqB). This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,371 @@ @import "compass" $interface-height: 90px $interface-color: rgba( 0, 155, 215, 1 ) =element-reset font: inherit -webkit-appearance: none -moz-appearance: none -ms-appearance: none outline: none border: none appearance: none background: none box-shadow: none border-radius: 0 padding: 0 margin: 0 select , button , input @include element-reset letter-spacing: 0.05em html , body , menu , main , nav , header , ul color: rgba( $interface-color , 0.75 ) letter-spacing: 0.05em position: absolute overflow: hidden font-family: sans-serif font-weight: 100 right: 0 left: 0 html , body bottom: 0 top: 0 menu , nav background-color: rgba( $interface-color , 0.85 ) height: $interface-height color: white menu top: 0 main top: $interface-height bottom: $interface-height nav white-space: nowrap vertical-align: middle bottom: 0 h1 text-transform: uppercase position: relative display: inline-block line-height: $interface-height font-size: 32px height: $interface-height padding: 0 15px label font-size: 13px padding: 5px 15px display: block select background-color: white border: 2px solid white color: $interface-color vertical-align: middle display: inline-block margin: -30px 15px 0 0 padding: 10px 35px 10px 10px cursor: pointer position: relative z-index: 2 i position: absolute display: inline-block pointer-events: none border-top: 7px solid $interface-color border-left: 5px solid transparent border-right: 5px solid transparent margin-top: 43px margin-left: -35px z-index: 2 ul top: 0 bottom: 0 right: 64px .slider display: inline-block position: absolute height: 100% width: auto white-space: nowrap .word position: relative display: inline-block height: 100% background-color: rgba( $interface-color , 0.1 ) width: 300px vertical-align: middle transition: background 0.1s ease-in-out &:nth-of-type( even ) background-color: rgba( $interface-color , 0.025 ) &.playing , &:nth-of-type( even ).playing background-color: rgba( $interface-color , 0.5 ) color: white &:hover .settings * transition-delay: 0s opacity: 1 .string font-weight: 100 width: 250px color: white text-align: center line-height: 36px height: 36px left: 50% top: 50% background-color: rgba( $interface-color , 0.75 ) position: absolute transform: translate( -50% , -50% ) .position position: absolute bottom: 140px right: 0 left: 0 top: 0 .settings position: absolute background-color: rgba( $interface-color , 0.1 ) text-align: center width: 100% height: 140px bottom: 0 * transition: opacity 0.15s ease-in-out transition-delay: 0.25s opacity: 0 .play , .delete margin: 10px 10px 0 10px border-radius: 50% background-color: rgba( $interface-color , 0.4 ) cursor: pointer position: relative display: inline-block height: 25px width: 25px .play::before content: "" position: absolute top: 50% left: 50% border-left: 8px solid white border-top: 5px solid transparent border-bottom: 5px solid transparent margin: -5px 0 0 -3px .delete &:before , &:after content: "" backface-visibility: hidden position: absolute background-color: white margin: -5px 0 0 -1px height: 10px width: 2px left: 50% top: 50% &:before transform: rotate( -45deg ) &:after transform: rotate( 45deg ) label display: block text-align: left input[type="range"] border-radius: 30px background-color: rgba( $interface-color , 0.4 ) padding: 3px width: 264px margin-bottom: 15px input[type=range]::-webkit-slider-thumb @include element-reset background-color: white border-radius: 50% cursor: grab width: 7px height: 7px &:active cursor: grabbing .add-words position: absolute border-radius: 50% background-color: rgba( $interface-color , 0.75 ) cursor: pointer margin: -15px 15px 0 0 width: 30px height: 30px right: 0 top: 50% &:before , &:after content: "" height: 2px background-color: white position: absolute margin: -1px 0 0 -7px width: 14px left: 50% top: 50% &:before transform: rotate( 90deg ) .play-state display: inline-block width: $interface-height/2 height: $interface-height/2 margin: $interface-height/4 15px overflow: hidden box-sizing: border-box border: 2px solid white border-radius: 50% position: relative cursor: pointer &:before , &:after content: "" position: absolute transition: transform 0.1s ease-in-out &:before border-left: 10px solid white border-top: 7px solid transparent border-bottom: 7px solid transparent margin: -6px 0 0 -4px left: 50% top: 50% transform: translate( 0, 0 ) &:after content: "" position: absolute background-color: white box-shadow: 9px 0 0 0 white margin: -8px 0 0 -8px width: 5px height: 16px transform: translate( $interface-height/3 , 0 ) &.playing &:before transform: translate( -$interface-height/3 , 0 ) &:after transform: translate( 0, 0 ) .export , .import cursor: pointer border: 2px solid white vertical-align: middle padding: 10px margin: -30px 15px 0 0 display: inline-block color: white aside position: absolute background-color: rgba( $interface-color , 0.9 ) left: 0 top: 100% width: 100% height: 100% overflow: hidden vertical-align: middle color: white opacity: 0 transition: transform 0.25s ease-in-out, opacity 0.25s ease-in-out, top 0s linear 0.25s &.active transition: transform 0.25s ease-in-out, opacity 0.25s ease-in-out, top 0s linear 0s opacity: 1 top: 0 &.exporting .importing display: none &.importing .exporting display: none .center transform: translate( -50% , -50% ) max-width: 90% width: 800px display: inline-block text-align: left position: absolute left: 50% top: 50% textarea @include element-reset font-family: monospace border: 2px solid white color: white box-sizing: border-box min-height: 350px padding: 15px margin: 15px 0 position: relative display: block width: 100% resize: none .close position: absolute border-radius: 50% background-color: white cursor: pointer margin: 0 width: 30px height: 30px right: 0 top: -10px &:before , &:after content: "" height: 2px backface-visibility: hidden background-color: $interface-color position: absolute margin: -1px 0 0 -7px width: 14px left: 50% top: 50% &:before transform: rotate( -45deg ) &:after transform: rotate( 45deg ) .import, .export display: none