├── server
├── __init__.py
├── requirements.txt
├── index.html
├── main.py
├── mary.py
└── vision.py
├── .gitignore
├── static
├── audio
│ ├── crash.mp3
│ ├── end.mp3
│ ├── fill0.mp3
│ ├── fill1.mp3
│ ├── fill2.mp3
│ ├── racer.mp3
│ ├── siren.mp3
│ ├── shutter.mp3
│ ├── applause.mp3
│ ├── voice
│ │ ├── yeah.mp3
│ │ ├── yeahup.mp3
│ │ ├── alright.mp3
│ │ ├── herewego.mp3
│ │ ├── herewegogo.mp3
│ │ ├── readytoexplore.mp3
│ │ └── takeapicture.mp3
│ └── reverseCrash.mp3
├── images
│ ├── camera.png
│ ├── rotate.png
│ ├── shutter.png
│ ├── x_button.png
│ ├── giorgio_left.png
│ ├── giorgio_right.png
│ ├── face
│ │ ├── giorgio_logo_anim__0000_eyes_right.png
│ │ ├── giorgio_logo_anim__0002_mouth_open.png
│ │ ├── giorgio_logo_anim__0009_big_notes_1.png
│ │ ├── giorgio_logo_anim__0010_big_notes_2.png
│ │ ├── giorgio_logo_anim__0011_big_notes_3.png
│ │ ├── giorgio_logo_anim__0012_big_notes_4.png
│ │ ├── giorgio_logo_anim__0013_big_notes_5.png
│ │ ├── giorgio_logo_anim__0003_little_note_1.png
│ │ ├── giorgio_logo_anim__0004_little_note_2.png
│ │ ├── giorgio_logo_anim__0005_little_note_3.png
│ │ ├── giorgio_logo_anim__0006_little_note_4.png
│ │ ├── giorgio_logo_anim__0007_little_note_5.png
│ │ ├── giorgio_logo_anim__0008_little_note_6.png
│ │ └── giorgio_logo_anim__0001_eyes_right_blink.png
│ ├── camera_icon.svg
│ ├── play_button.svg
│ ├── badgeAI_master.svg
│ └── badgeFriends_master.svg
├── style
│ ├── pixel_operator
│ │ ├── PixelOperator.ttf
│ │ ├── PixelOperator8.ttf
│ │ ├── PixelOperatorHB.ttf
│ │ ├── PixelOperatorSC.ttf
│ │ ├── PixelOperatorHB8.ttf
│ │ ├── PixelOperatorHBSC.ttf
│ │ ├── PixelOperatorMono.ttf
│ │ ├── PixelOperator-Bold.ttf
│ │ ├── PixelOperator8-Bold.ttf
│ │ ├── PixelOperatorMono8.ttf
│ │ ├── PixelOperatorMonoHB.ttf
│ │ ├── PixelOperatorMonoHB8.ttf
│ │ ├── PixelOperatorSC-Bold.ttf
│ │ ├── PixelOperatorMono-Bold.ttf
│ │ ├── PixelOperatorMono8-Bold.ttf
│ │ └── LICENSE.txt
│ ├── notsupported.css
│ ├── close.css
│ ├── title.css
│ ├── lines.css
│ ├── common.css
│ ├── nocamera.css
│ ├── main.css
│ ├── text.css
│ ├── shutter.css
│ ├── camera.css
│ ├── about.css
│ └── splash.css
├── index.html
├── src
│ ├── Visible.js
│ ├── Config.js
│ ├── music
│ │ ├── Timeline.js
│ │ ├── Fill.js
│ │ ├── Music.js
│ │ ├── Position.js
│ │ └── Background.js
│ ├── interface
│ │ ├── NoCamera.js
│ │ ├── Loader.js
│ │ ├── Orientation.js
│ │ ├── Close.js
│ │ ├── Shutter.js
│ │ ├── Splash.js
│ │ ├── About.js
│ │ └── Title.js
│ ├── FeatureTest.js
│ ├── voice
│ │ ├── Intro.js
│ │ ├── Lines.js
│ │ ├── SpeakBuffer.js
│ │ ├── Filler.js
│ │ ├── Lyrics.js
│ │ ├── VoiceEffects.js
│ │ ├── Wait.js
│ │ ├── Voice.js
│ │ └── Text.js
│ ├── camera
│ │ ├── Label.js
│ │ ├── Camera.js
│ │ ├── Image.js
│ │ └── Canvas.js
│ └── Main.js
├── package.json
├── webpack.config.js
└── third_party
│ └── Modernizr.js
├── app.yaml
├── CONTRIBUTING.md
└── README.md
/server/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/requirements.txt:
--------------------------------------------------------------------------------
1 | google-api-python-client==1.5.1
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | server/data/key.json
2 | node_modules/
3 | .DS_Store
4 | *.pyc
5 | static/build/*
6 | static/images/font/
--------------------------------------------------------------------------------
/static/audio/crash.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/crash.mp3
--------------------------------------------------------------------------------
/static/audio/end.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/end.mp3
--------------------------------------------------------------------------------
/static/audio/fill0.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/fill0.mp3
--------------------------------------------------------------------------------
/static/audio/fill1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/fill1.mp3
--------------------------------------------------------------------------------
/static/audio/fill2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/fill2.mp3
--------------------------------------------------------------------------------
/static/audio/racer.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/racer.mp3
--------------------------------------------------------------------------------
/static/audio/siren.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/siren.mp3
--------------------------------------------------------------------------------
/static/audio/shutter.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/shutter.mp3
--------------------------------------------------------------------------------
/static/images/camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/camera.png
--------------------------------------------------------------------------------
/static/images/rotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/rotate.png
--------------------------------------------------------------------------------
/static/audio/applause.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/applause.mp3
--------------------------------------------------------------------------------
/static/audio/voice/yeah.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/voice/yeah.mp3
--------------------------------------------------------------------------------
/static/images/shutter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/shutter.png
--------------------------------------------------------------------------------
/static/images/x_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/x_button.png
--------------------------------------------------------------------------------
/static/audio/reverseCrash.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/reverseCrash.mp3
--------------------------------------------------------------------------------
/static/audio/voice/yeahup.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/voice/yeahup.mp3
--------------------------------------------------------------------------------
/static/audio/voice/alright.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/voice/alright.mp3
--------------------------------------------------------------------------------
/static/audio/voice/herewego.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/voice/herewego.mp3
--------------------------------------------------------------------------------
/static/images/giorgio_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/giorgio_left.png
--------------------------------------------------------------------------------
/static/images/giorgio_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/giorgio_right.png
--------------------------------------------------------------------------------
/static/audio/voice/herewegogo.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/voice/herewegogo.mp3
--------------------------------------------------------------------------------
/static/audio/voice/readytoexplore.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/voice/readytoexplore.mp3
--------------------------------------------------------------------------------
/static/audio/voice/takeapicture.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/audio/voice/takeapicture.mp3
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperator.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperator.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperator8.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperator8.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorHB.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorHB.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorSC.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorSC.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorHB8.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorHB8.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorHBSC.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorHBSC.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorMono.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperator-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperator-Bold.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperator8-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperator8-Bold.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorMono8.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorMono8.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorMonoHB.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorMonoHB.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorMonoHB8.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorMonoHB8.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorSC-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorSC-Bold.ttf
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorMono-Bold.ttf
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0000_eyes_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0000_eyes_right.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0002_mouth_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0002_mouth_open.png
--------------------------------------------------------------------------------
/static/style/pixel_operator/PixelOperatorMono8-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/style/pixel_operator/PixelOperatorMono8-Bold.ttf
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0009_big_notes_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0009_big_notes_1.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0010_big_notes_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0010_big_notes_2.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0011_big_notes_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0011_big_notes_3.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0012_big_notes_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0012_big_notes_4.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0013_big_notes_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0013_big_notes_5.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0003_little_note_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0003_little_note_1.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0004_little_note_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0004_little_note_2.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0005_little_note_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0005_little_note_3.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0006_little_note_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0006_little_note_4.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0007_little_note_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0007_little_note_5.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0008_little_note_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0008_little_note_6.png
--------------------------------------------------------------------------------
/static/images/face/giorgio_logo_anim__0001_eyes_right_blink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/aiexperiments-giorgio-cam/HEAD/static/images/face/giorgio_logo_anim__0001_eyes_right_blink.png
--------------------------------------------------------------------------------
/server/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/static/images/camera_icon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/static/src/Visible.js:
--------------------------------------------------------------------------------
1 | import Visibility from 'visibilityjs'
2 |
3 | export default function resolveIfVisible(response){
4 | if (Visibility.hidden()){
5 | return new Promise((success) => {
6 | const id = Visibility.onVisible(() => {
7 | //and a short pause to give the
8 | //clock a chance to catch up
9 | setTimeout(() => {
10 | Visibility.unbind(id)
11 | success(response)
12 | }, 500)
13 | })
14 | })
15 | } else {
16 | return Promise.resolve(response)
17 | }
18 | }
--------------------------------------------------------------------------------
/static/images/play_button.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
--------------------------------------------------------------------------------
/static/style/notsupported.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #notsupported {
18 | position: relative;
19 | margin: 200px auto 0px auto;
20 | width: 80%;
21 | min-width: 300px;
22 | font-size: 40px;
23 | line-height: 50px;
24 | text-align: center;
25 | color: white;
26 |
27 | a {
28 | color: white;
29 | text-decoration: underline;
30 | cursor: pointer;
31 | }
32 | }
--------------------------------------------------------------------------------
/server/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2016 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | import os
19 | import webapp2
20 | from google.appengine.ext.webapp import template
21 |
22 |
23 | class Main(webapp2.RequestHandler):
24 |
25 | def get(self):
26 | main = os.path.join(os.path.dirname(__file__), 'index.html')
27 | self.response.out.write(template.render(main, {}))
28 |
29 |
30 | app = webapp2.WSGIApplication([
31 | ('/', Main)
32 | ], debug=True)
33 |
--------------------------------------------------------------------------------
/static/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "giorgio-cam",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "Yotam Mann",
10 | "dependencies": {
11 | "autoprefixer": "^6.3.7",
12 | "babel-core": "^6.11.4",
13 | "babel-loader": "^6.2.4",
14 | "babel-preset-es2015": "^6.9.0",
15 | "css-loader": "^0.23.1",
16 | "domready": "^1.0.8",
17 | "exports-loader": "^0.6.3",
18 | "file-loader": "^0.9.0",
19 | "is-mobile": "^0.2.2",
20 | "knuth-shuffle": "^1.0.1",
21 | "postcss-calc": "^5.3.0",
22 | "postcss-loader": "^0.9.1",
23 | "precss": "^1.4.0",
24 | "screenfull": "^3.0.2",
25 | "startaudiocontext": "^1.2.0",
26 | "style-loader": "^0.13.1",
27 | "three": "^0.81.0",
28 | "tone": "^0.8.0",
29 | "tween.js": "^16.3.5",
30 | "url-loader": "^0.5.7",
31 | "visibilityjs": "^1.2.4",
32 | "webpack": "^1.13.1",
33 | "webrtc-adapter": "^2.0.3",
34 | "youtube-iframe": "^1.0.3"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/static/src/Config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Transport from 'Tone/core/Transport'
18 | import Time from 'Tone/type/Time'
19 |
20 | Transport.bpm.value = 128
21 |
22 | export default {
23 | startingPosition: 0,
24 | cloudVision : true,
25 | quantizeLevel : '4n',
26 | name : 'Giorgio Cam',
27 | intro : true,
28 | aiExperimentsLink : 'https://aiexperiments.withgoogle.com',
29 | //mock throws a quota limit error
30 | quotaLimit : false,
31 | fallbackScreen : false,
32 | fullscreen : false || window.location.search.indexOf('fullscreen') !== -1,
33 | fillTimeline : '138m',
34 | fadeOutTime : Time('4m').toSeconds()
35 | }
--------------------------------------------------------------------------------
/app.yaml:
--------------------------------------------------------------------------------
1 | runtime: python27
2 | api_version: 1
3 | threadsafe: true
4 |
5 |
6 | handlers:
7 |
8 | - url: /
9 | script: server.main.app
10 | secure: always
11 |
12 | - url: /speak/.*
13 | script: server.mary.app
14 |
15 | - url: /see
16 | script: server.vision.app
17 |
18 | - url: /(.*\.js)
19 | mime_type: text/javascript
20 | static_files: static/\1
21 | upload: static/(.*\.js)
22 |
23 | - url: /(.*\.html)
24 | mime_type: text/html
25 | static_files: static/\1
26 | upload: static/(.*\.html)
27 |
28 | - url: /(.*\.(bmp|gif|ico|jpeg|jpg|png|svg|ttf))
29 | static_files: static/\1
30 | upload: static/(.*\.(bmp|gif|ico|jpeg|jpg|png|svg|ttf))
31 |
32 | # index files
33 | - url: /(.+)/
34 | static_files: static/\1/index.html
35 | upload: static/(.+)/index.html
36 |
37 | # audio files
38 | - url: /(.*\.mp3)
39 | mime_type: audio/mpeg
40 | static_files: static/\1
41 | upload: static/audio/(.*\.mp3)
42 |
43 |
44 | skip_files:
45 | - ^(.*/)?.*\.pyc
46 | - ^(.*/)?.*\.wav
47 | - ^.git/.*
48 | - ^(.*/)?node_modules/.*
49 | - ^static/src/.*
50 | - ^static/style/.*
51 | - ^package\.json
52 |
53 | libraries:
54 | - name: webapp2
55 | version: latest
56 |
57 | env_variables:
58 | GOOGLE_APPLICATION_CREDENTIALS: PATH/TO/CLOUD_VISION_KEY.json
--------------------------------------------------------------------------------
/static/style/close.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | @import 'common.css';
18 |
19 | #closeButton {
20 | @mixin closeButton;
21 | }
22 |
23 | #end {
24 | position: absolute;
25 | top: 0px;
26 | left: 0px;
27 | width: 100%;
28 | height: 100%;
29 | z-index: 100002;
30 | font-weight: bold;
31 | color: white;
32 |
33 | a {
34 | @mixin yellowLink;
35 | }
36 |
37 | #text {
38 | position: absolute;
39 | width: 100%;
40 | line-height: 60px;
41 | font-size: 32px;
42 | top: 50%;
43 | transform: translate(0%, -50%);
44 | letter-spacing: 1px;
45 | text-align: center;
46 |
47 | #clickHere {
48 | @mixin yellowLink;
49 | }
50 |
51 | }
52 | }
--------------------------------------------------------------------------------
/static/style/title.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #title {
18 | height: 33px;
19 | position: relative;
20 | width: auto;
21 | display: inline-block;
22 |
23 | * {
24 | display: inline-block;
25 | float: left;
26 | height: 100%;
27 | background-size: auto 100%;
28 | background-repeat: no-repeat;
29 | }
30 |
31 | #giorgi {
32 | width: 146px;
33 | background-image: url(../images/giorgio_left.png);
34 | }
35 |
36 | #face {
37 | width: 57px;
38 | background-image: url(../images/face/giorgio_logo_anim__0000_eyes_right.png);
39 |
40 | img {
41 | width: 100%;
42 | height: 100%;
43 | }
44 | }
45 |
46 | #cam {
47 | width: 84px;
48 | background-image: url(../images/giorgio_right.png);
49 | }
50 | }
--------------------------------------------------------------------------------
/static/src/music/Timeline.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Transport from 'Tone/core/Transport'
18 | import Loop from 'Tone/event/Loop'
19 | import Background from 'music/Background'
20 | import Time from 'Tone/type/Time'
21 | import Fill from 'music/Fill'
22 | import Config from 'Config'
23 | import Master from 'Tone/core/Master'
24 |
25 | export default class Timeline{
26 | constructor(){
27 |
28 |
29 | }
30 |
31 | start(){
32 | let startTime = Transport.now() + 0.7
33 | Transport.start(startTime, Config.intro ? 0 : '2m')
34 | Transport.loopStart = '10m'
35 | Transport.loopEnd = '18m'
36 | Transport.loop = true;
37 | return startTime
38 | }
39 |
40 | stop(){
41 | Transport.stop()
42 | Transport.clear(0)
43 | }
44 |
45 | next(){
46 |
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/static/style/lines.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #textContainer {
18 | position: absolute;
19 | top: 0px;
20 | left: 0px;
21 | width: 100%;
22 | height: 100%;
23 | background-color: rgba(0, 0, 0, 0.2);
24 | pointer-events: none;
25 |
26 | #textLine {
27 | position: absolute;
28 | width: calc(100% - 40px);
29 | max-width: 500px;
30 | text-align: center;
31 | color: white;
32 | pointer-events: none;
33 | font-size: 46px;
34 | top: 50%;
35 | left: 50%;
36 | transform: translate(-50%, -50%);
37 |
38 | span {
39 | margin-right: 10px;
40 | word-wrap: normal;
41 | display: inline-block;
42 | opacity: 0;
43 |
44 | &.label {
45 | text-decoration: underline;
46 | }
47 |
48 | &.visible {
49 | opacity: 1;
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/static/style/common.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | $closeSize : 25px;
18 |
19 | @define-mixin closeButton {
20 | position: absolute;
21 | top: 20px;
22 | right: 20px;
23 | width: $closeSize;
24 | height: $closeSize;
25 | background-image: url(../images/x_button.png);
26 | background-size: 100% 100%;
27 | color: white;
28 | pointer-events: none;
29 | opacity: 0;
30 | cursor: pointer;
31 | z-index: 100001;
32 | transition: transform 0.2s;
33 | -webkit-tap-highlight-color: rgba(0,0,0,0);
34 |
35 | &.visible {
36 | opacity: 1;
37 | pointer-events: initial;
38 |
39 | &:hover {
40 | transform: scale(1.1);
41 |
42 | &:active {
43 | opacity: 0.4;
44 | }
45 | }
46 |
47 | }
48 | }
49 |
50 | @define-mixin yellowLink {
51 | color: rgb(251, 240, 57);
52 | text-decoration: underline;
53 | cursor: pointer;
54 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Want to contribute? Great! First, read this page (including the small print at the end).
2 |
3 | ### Before you contribute
4 | Before we can use your code, you must sign the
5 | [Google Individual Contributor License Agreement]
6 | (https://cla.developers.google.com/about/google-individual)
7 | (CLA), which you can do online. The CLA is necessary mainly because you own the
8 | copyright to your changes, even after your contribution becomes part of our
9 | codebase, so we need your permission to use and distribute your code. We also
10 | need to be sure of various other things—for instance that you'll tell us if you
11 | know that your code infringes on other people's patents. You don't have to sign
12 | the CLA until after you've submitted your code for review and a member has
13 | approved it, but you must do it before we can put your code into our codebase.
14 | Before you start working on a larger contribution, you should get in touch with
15 | us first through the issue tracker with your idea so that we can help out and
16 | possibly guide you. Coordinating up front makes it much easier to avoid
17 | frustration later on.
18 |
19 | ### Code reviews
20 | All submissions, including submissions by project members, require review. We
21 | use Github pull requests for this purpose.
22 |
23 | ### The small print
24 | Contributions made by corporations are covered by a different agreement than
25 | the one above, the
26 | [Software Grant and Corporate Contributor License Agreement]
27 | (https://cla.developers.google.com/about/google-corporate).
--------------------------------------------------------------------------------
/static/style/nocamera.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | @import 'common.css';
18 |
19 |
20 | #nocamera {
21 | width: 100%;
22 | height: 100%;
23 | position: absolute;
24 | top: 0px;
25 | left: 0px;
26 | background-color: black;
27 | z-index: 10000;
28 |
29 | #content {
30 | position: absolute;
31 | top: 50%;
32 | left: 50%;
33 | width: 205px;
34 | transform: translate(-50%, -50%);
35 | text-align: center;
36 | font-family: 'Pixel Operator', monospace;
37 |
38 | * {
39 | position: relative;
40 | margin-top: 20px;
41 | font-size: 20px;
42 | color: white;
43 | }
44 |
45 | $iconSize : 60px;
46 |
47 | #camera {
48 | background-image: url(../images/camera.png);
49 | background-size: 100% 100%;
50 | width: $iconSize;
51 | height: $iconSize;
52 | margin: 10px auto 30px auto;
53 | }
54 |
55 | #blurb {
56 | text-transform: none;
57 | }
58 |
59 | #restart {
60 | @mixin yellowLink;
61 | }
62 |
63 | }
64 |
65 | }
--------------------------------------------------------------------------------
/static/src/interface/NoCamera.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import 'style/nocamera.css'
18 |
19 | export default function NoCamera(container){
20 |
21 | const background = document.createElement('div')
22 | background.id = 'nocamera'
23 | container.appendChild(background)
24 |
25 | const content = document.createElement('div')
26 | content.id = 'content'
27 | background.appendChild(content)
28 |
29 | const img = document.createElement('div')
30 | img.id = 'camera'
31 | content.appendChild(img)
32 |
33 | const blurb = document.createElement('div')
34 | blurb.id = 'blurb'
35 | blurb.textContent = 'Since you didn\'t allow access to the camera, this experiment won\'t work.'
36 | content.appendChild(blurb)
37 |
38 | /*const restartButton = document.createElement('div')
39 | restartButton.id = 'restart'
40 | restartButton.textContent = 'Please restart'
41 | content.appendChild(restartButton)
42 | restartButton.addEventListener('click', () => {
43 | window.location.reload()
44 | })*/
45 | }
--------------------------------------------------------------------------------
/static/style/main.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | @font-face{
18 | font-family: 'Pixel Operator Bold';
19 | src : url(pixel_operator/PixelOperator-Bold.ttf);
20 | }
21 |
22 | @font-face{
23 | font-family: 'Pixel Operator';
24 | src : url(pixel_operator/PixelOperator.ttf);
25 | }
26 |
27 | body {
28 | font-family: 'Pixel Operator Bold', monospace;
29 | position: absolute;
30 | top: 0px;
31 | left: 0px;
32 | width: 100%;
33 | height: 100%;
34 | margin: 0px;
35 | background-size: 100% 100%;
36 | background-color: black;
37 | color: white;
38 | overflow: hidden;
39 | text-transform: uppercase;
40 |
41 | *:focus {
42 | outline: none;
43 | user-select: none;
44 | }
45 |
46 | #orientationBlocker {
47 | position: absolute;
48 | z-index: 1000000000;
49 | top: 0px;
50 | left: 0px;
51 | width: 100%;
52 | height: 100%;
53 | background-color: black;
54 | background-image: url(../images/rotate.png);
55 | background-repeat: no-repeat;
56 | background-position: center center;
57 | background-size: 100% auto;
58 | }
59 | }
--------------------------------------------------------------------------------
/static/src/FeatureTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import 'style/main.css'
18 | import domReady from 'domready'
19 | import 'style/splash.css'
20 | import Modernizr from 'exports?Modernizr!Modernizr'
21 |
22 | require.ensure(['Main', 'style/notsupported.css'], (require) => {
23 |
24 | domReady(() => {
25 |
26 | if (Modernizr.getusermedia && Modernizr.webaudio && Modernizr.webgl){
27 |
28 | const main = require('Main')
29 |
30 | } else {
31 |
32 | require('style/notsupported.css')
33 |
34 | const text = document.createElement('div')
35 | text.id = 'notsupported'
36 | if (!Modernizr.getusermedia){
37 | text.innerHTML = 'Sorry, your device doesn’t allow access to the camera. Try using Chrome on an Android phone or a laptop.'
38 | } else {
39 | text.innerHTML = 'Oops, sorry for the tech trouble. For the best experience view in Chrome Browser'
40 | }
41 | document.body.appendChild(text)
42 |
43 | }
44 |
45 | })
46 |
47 | })
48 |
49 |
50 |
--------------------------------------------------------------------------------
/static/src/music/Fill.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Player from 'Tone/source/Player'
18 | import Transport from 'Tone/core/Transport'
19 | import Config from 'Config'
20 | import MusicPosition from 'music/Position'
21 |
22 | export default class Fill{
23 | constructor(){
24 | this._beats = {
25 | 0 : new Player('./audio/fill0.mp3').toMaster(),
26 | 1 : new Player({ url : './audio/fill1.mp3', volume : -2}).toMaster(),
27 | 2 : new Player({ url : './audio/fill2.mp3', volume : -3}).toMaster(),
28 | }
29 | this._currentPlaying = this._beats[0]
30 |
31 | //loop all of them
32 | for (let b in this._beats){
33 | this._beats[b].loop = true
34 | this._beats[b].loopStart = '5m'
35 | this._beats[b].loopEnd = '6m'
36 | }
37 |
38 | this._crash = new Player('./audio/crash.mp3').toMaster()
39 | }
40 |
41 | fill(){
42 | this._currentPlaying = this._beats[MusicPosition.fillPosition]
43 | this._currentPlaying.start(`@${Config.quantizeLevel}`, MusicPosition.end ? '2m' : 0)
44 | }
45 |
46 | fillEnd(){
47 | this._currentPlaying.stop(`@${Config.quantizeLevel}`)
48 | this._crash.start(`@${Config.quantizeLevel} - 8n`)
49 | }
50 |
51 | stop(){
52 | this._currentPlaying.stop()
53 | this._crash.stop()
54 | }
55 | }
--------------------------------------------------------------------------------
/static/src/interface/Loader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Buffer from 'Tone/core/Buffer'
18 | import Tone from 'Tone/core/Tone'
19 | import events from 'events'
20 | const EventEmitter = events.EventEmitter
21 | import StartAudioContext from 'startaudiocontext'
22 |
23 | export default class Loader extends EventEmitter{
24 | constructor(container){
25 | super()
26 |
27 | const loader = document.createElement('div')
28 | loader.id = 'loader'
29 | container.appendChild(loader)
30 |
31 | const loaderText = document.createElement('div')
32 | loaderText.id = 'loaderText'
33 | loaderText.textContent = 'loading'
34 | loader.appendChild(loaderText)
35 |
36 | const fill = document.createElement('div')
37 | fill.id = 'fill'
38 | loader.appendChild(fill)
39 |
40 | const fillText = document.createElement('div')
41 | fillText.id = 'fillText'
42 | fillText.textContent = 'loading'
43 | fill.appendChild(fillText)
44 |
45 | StartAudioContext(Tone.context, loader)
46 |
47 | Buffer.on('load', () => {
48 |
49 | fillText.textContent = 'Let\'s go'
50 | loader.classList.add('clickable')
51 |
52 | loader.addEventListener('click', () => {
53 |
54 | this.emit('start')
55 | })
56 | })
57 |
58 | Buffer.on('progress', (prog) => {
59 |
60 | fill.style.width = `${(prog * 100).toFixed(2)}%`
61 |
62 | })
63 | }
64 | }
--------------------------------------------------------------------------------
/static/src/voice/Intro.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import SpeakBuffer from 'voice/SpeakBuffer'
18 | import Time from 'Tone/type/Time'
19 | import Lines from 'voice/Lines'
20 | import Config from 'Config'
21 | import AudioBuffers from 'Tone/core/Buffers'
22 |
23 | export default class Intro {
24 |
25 | constructor(){
26 | this._didIntro = false
27 |
28 | this._buffers = ['Ready to rock image recognition with me?', 'Take a picture and I\'ll tell you what i see']
29 | this._buffers.forEach((text, index) => {
30 | SpeakBuffer.getBuffer(text, 1.25, -1).then((buffer) => {
31 | this._buffers[index] = {text, buffer}
32 | })
33 | })
34 |
35 | /*this._buffers = new AudioBuffers(['readytoexplore.mp3', 'takeapicture.mp3'], undefined, 'audio/voice/')
36 | this._text = ['Ready to explore Image Recognition with me?', 'Take a picture and I\'ll tell you what I see.']*/
37 | }
38 |
39 | start(time){
40 | if (Config.intro && !this._didIntro){
41 | this._didIntro = true
42 | const lines = Lines.intro
43 | // SpeakBuffer.play(this._buffers[1].text, this._buffers[1].buffer, Time(time).add('2n'), 'dry')
44 | SpeakBuffer.play(this._buffers[0].text, this._buffers[0].buffer, Time(time), 'dry')
45 | return SpeakBuffer.play(this._buffers[1].text, this._buffers[1].buffer, Time(time).add('1m'), 'dry')
46 | } else {
47 | return Promise.resolve()
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/static/style/text.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #textContainer {
18 | position: absolute;
19 | top: 0px;
20 | left: 0px;
21 | width: 100%;
22 | height: 100%;
23 | z-index: 10000;
24 | background-color: rgba(0, 0, 0, 0.3);
25 | pointer-events: none;
26 |
27 |
28 | .utterance {
29 |
30 | width: 80%;
31 | min-width: 300px;
32 | text-align: center;
33 | position: absolute;
34 | top: 50%;
35 | left: 50%;
36 | transform: translate(-50%, -50%);
37 |
38 | &.shiftUp {
39 | top: 45%;
40 | }
41 |
42 | span {
43 | margin: 0 10px 0 10px;
44 | visibility: hidden;
45 | display: inline-block;
46 | font-size: 50px;
47 | line-height: 70px;
48 |
49 | &.visible {
50 | visibility: initial;
51 | }
52 |
53 | $labelMargin: 20px;
54 |
55 | &.label {
56 |
57 |
58 | display: block;
59 | margin: $labelMargin 0px $labelMargin 0px;
60 |
61 | $spanMargin : 3px;
62 |
63 | span {
64 | margin: $spanMargin $spanMargin $spanMargin;
65 | line-height: calc(70px - $spanMargin * 2);
66 | padding: 10px;
67 | background-color: black;
68 | }
69 |
70 | div.score {
71 | visibility: hidden;
72 | width: 100%;
73 | font-size: 18px;
74 | line-height: 26px;
75 | text-align: center;
76 | text-transform: uppercase;
77 |
78 | &.visible {
79 | visibility: initial;
80 | }
81 | }
82 | }
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/static/src/camera/Label.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Config from 'Config'
18 |
19 | export default function Label(canvas){
20 |
21 | if (Config.quotaLimit){
22 | return new Promise((success, fail) => {
23 | setTimeout(() => {
24 | fail('quota')
25 | }, 2000)
26 | })
27 | } else if (Config.cloudVision){
28 | let formData = new FormData();
29 | formData.append('image', canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''));
30 |
31 |
32 | return new Promise((success, fail) => {
33 | let request = new XMLHttpRequest();
34 | request.timeout = 20000
35 | request.open('POST', '/see');
36 | request.addEventListener('load', () => {
37 | if (request.status == 200) {
38 | const response = JSON.parse(request.response)
39 | if (Array.isArray(response)){
40 | success(JSON.parse(request.response))
41 | } else if (response.error){
42 | fail(response.error)
43 | }
44 | } else {
45 | fail(request.status)
46 | }
47 | })
48 | request.addEventListener('error', fail)
49 | request.addEventListener('timeout', fail)
50 | request.send(formData)
51 | })
52 | } else {
53 | return new Promise((success) => {
54 | setTimeout(() => {
55 | success([{
56 | label : 'this is a very long multi word label',
57 | score : 0.8
58 | }, {
59 | label : 'this is another very long multi word label multi word',
60 | score : 0.8
61 | }])
62 | }, 200)
63 | })
64 | }
65 |
66 | }
--------------------------------------------------------------------------------
/static/src/interface/Orientation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Master from 'Tone/core/Master'
18 |
19 | const orientationBlocker = document.createElement('orientationBlocker')
20 | orientationBlocker.id = 'orientationBlocker'
21 | let hasBlocker = false
22 |
23 | function isLandscape(angle){
24 | const landscape = Math.abs(angle) === 90 || Math.abs(angle) === 270
25 | if (landscape){
26 | if (!hasBlocker){
27 | hasBlocker = true
28 | document.body.appendChild(orientationBlocker)
29 | Master.mute = true
30 | }
31 | } else {
32 | if (hasBlocker){
33 | hasBlocker = false
34 | orientationBlocker.remove()
35 | Master.mute = false
36 | }
37 | }
38 | }
39 |
40 | window.addEventListener('orientationchange', () => {
41 | if (window.orientation){
42 | isLandscape(window.orientation)
43 | }
44 | });
45 |
46 | if (window.screen && window.screen.orientation){
47 | window.screen.orientation.addEventListener('change', () => {
48 | if (window.screen && window.screen.orientation){
49 | isLandscape(window.screen.orientation.angle)
50 | }
51 | });
52 | }
53 |
54 | //test initially
55 | function testScreenOrientation(){
56 | if (window.screen && window.screen.orientation){
57 | isLandscape(window.screen.orientation.angle)
58 | } else if (typeof window.orientation === 'number'){
59 | isLandscape(window.orientation)
60 | }
61 | }
62 |
63 | //test periodically to make sure we didn't miss it
64 | setInterval(function(){
65 | testScreenOrientation()
66 | }, 100)
--------------------------------------------------------------------------------
/static/src/voice/Lines.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import SpeakBuffer from 'voice/SpeakBuffer'
18 | import AudioBuffer from 'Tone/core/Buffer'
19 |
20 | const introRate = 1.2
21 | const introPitch = -1
22 |
23 | const fillerRate = 1.3
24 | const fillerPitch = 2
25 |
26 | const lines = {
27 |
28 | }
29 |
30 | const lyrics = {
31 | /**
32 | * Spoken in error
33 | */
34 | end : [
35 | {
36 | text : 'I\'m too popular right now. I\'ve got to shut down.',
37 | // text : 'Too many people taking pictures right now. Please try later. Now I\'m shutting down.',
38 | rate : 0.9,
39 | pitch : fillerPitch
40 | },
41 | {
42 | text : 'Okay, I am shutting down',
43 | rate : 0.9,
44 | pitch : fillerPitch
45 | },
46 | {
47 | text : 'That\'s an error! Take another.',
48 | rate : 1.1,
49 | pitch : fillerPitch
50 | },
51 | ]
52 | }
53 |
54 | // load everything
55 | for (let section in lyrics){
56 | let lines = lyrics[section]
57 | if (Array.isArray(lines)){
58 | lines.map((line, i) => {
59 | if (line.url){
60 | lines[i].buffer = new AudioBuffer(`audio/voice/${line.url}`)
61 | } else {
62 | SpeakBuffer.getBuffer(line.text, line.rate, line.pitch).then( (buffer) => {
63 | lines[i].buffer = buffer
64 | })
65 | }
66 | })
67 | } else {
68 | SpeakBuffer.getBuffer(lines.text, lines.rate, lines.pitch).then( (buffer) => {
69 | lines.buffer = buffer
70 | })
71 | }
72 | }
73 |
74 | export default lyrics
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/static/src/interface/Close.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import events from 'events'
18 | import 'style/close.css'
19 | const EventEmitter = events.EventEmitter
20 |
21 | export default class Close extends EventEmitter{
22 |
23 | constructor(container){
24 | super()
25 |
26 | this._closeButton = document.createElement('div')
27 | this._closeButton.id = 'closeButton'
28 | container.appendChild(this._closeButton)
29 |
30 | this._closeButton.addEventListener('click', (e) => {
31 | e.preventDefault()
32 | // this.end()
33 | this._closeButton.remove()
34 | this.emit('click')
35 | })
36 |
37 | this._container = container
38 | }
39 |
40 | hide(){
41 | this._closeButton.classList.remove('visible')
42 | }
43 |
44 | show(){
45 | this._closeButton.classList.add('visible')
46 | }
47 |
48 | end(){
49 | //put up a big blocking screen with the option to restart
50 | const end = document.createElement('div')
51 | end.id = 'end'
52 | this._container.appendChild(end)
53 |
54 | const textContainer = document.createElement('div')
55 | textContainer.id = 'text'
56 | end.appendChild(textContainer)
57 |
58 |
59 | const restText = document.createElement('div')
60 | restText.id = 'rest'
61 | restText.innerHTML = 'restart or learn more
about how i work'
62 | textContainer.appendChild(restText)
63 |
64 | restText.querySelector('#clickHere').addEventListener('click', () => {
65 | this.emit('about')
66 | })
67 | }
68 | }
--------------------------------------------------------------------------------
/static/src/music/Music.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Transport from 'Tone/core/Transport'
18 | import Loop from 'Tone/event/Loop'
19 | import Background from 'music/Background'
20 | import Time from 'Tone/type/Time'
21 | import Fill from 'music/Fill'
22 | import Config from 'Config'
23 | import Master from 'Tone/core/Master'
24 | import MusicPosition from 'music/Position'
25 |
26 | Transport.bpm.value = 128
27 |
28 | export default class Music{
29 | constructor(){
30 |
31 | this._bgMusic = new Background()
32 |
33 | this._fill = new Fill()
34 |
35 | }
36 |
37 | start(){
38 | let startTime = Transport.now() + 0.7
39 | Transport.start(startTime)
40 | this._bgMusic.start(startTime)
41 | Master.mute = false
42 | return startTime
43 | }
44 |
45 | stop(){
46 | this._bgMusic.stop()
47 | this._fill.stop()
48 | // Master.volume.rampTo(-Infinity, 2)
49 | Transport.stop()
50 | Transport.clear(0)
51 | }
52 |
53 | fill(){
54 | if (Transport.state === 'started'){
55 | this._bgMusic.fill()
56 | this._fill.fill()
57 | }
58 | }
59 |
60 | endFill(){
61 | if (Transport.state === 'started'){
62 | this._bgMusic.fillEnd()
63 | this._fill.fillEnd()
64 | }
65 | return new Promise((done) => {
66 | Transport.scheduleOnce( (time) => {
67 | done(time)
68 | }, `@${Config.quantizeLevel}`)
69 | })
70 | }
71 |
72 | /**
73 | * load the text line from the server
74 | */
75 | loadLine(labels){
76 | return this._speaker.loadLine(labels)
77 | }
78 |
79 | end(){
80 | return this._bgMusic.end()
81 | }
82 |
83 | }
--------------------------------------------------------------------------------
/static/src/interface/Shutter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Player from 'Tone/source/Player'
18 | import Text from 'voice/Text'
19 | import events from 'events'
20 | import 'style/shutter.css'
21 | const EventEmitter = events.EventEmitter
22 |
23 | export default class Shutter extends EventEmitter{
24 |
25 | constructor(container){
26 | super()
27 |
28 | this._shutterButton = document.createElement('div')
29 | this._shutterButton.id = 'shutterButton'
30 | container.appendChild(this._shutterButton)
31 |
32 | const innerFill = document.createElement('div')
33 | innerFill.id = 'innerShutter'
34 | this._shutterButton.appendChild(innerFill)
35 |
36 | this._flashEl = document.createElement('div')
37 | this._flashEl.id = 'flash'
38 | container.appendChild(this._flashEl)
39 |
40 | this._shutterSound = new Player('./audio/shutter.mp3').toMaster()
41 |
42 | this._shutterButton.addEventListener('click', (e) => {
43 | e.preventDefault()
44 | this._flash()
45 | this.hide()
46 | this.emit('click')
47 | })
48 | }
49 |
50 | /**
51 | * flash the screen
52 | * on the screen like you're taking a pic
53 | */
54 | _flash(){
55 | this._shutterSound.start()
56 | this._flashEl.className = 'visible'
57 | setTimeout(() => {
58 | this._flashEl.className = ''
59 | }, 250)
60 | }
61 |
62 | hide(){
63 | this._shutterButton.classList.remove('visible')
64 | Text.shiftDown()
65 | }
66 |
67 | remove(){
68 | this._shutterButton.remove()
69 | }
70 |
71 | show(){
72 | this._shutterButton.classList.add('visible')
73 | Text.shiftUp()
74 | }
75 | }
--------------------------------------------------------------------------------
/static/webpack.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the 'License');
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an 'AS IS' BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | var webpack = require('webpack');
18 |
19 | var PROD = process.argv.indexOf('-p') !== -1
20 |
21 | var precss = require('precss');
22 | var calc = require("postcss-calc")
23 | var autoprefixer = require('autoprefixer');
24 |
25 | module.exports = {
26 | 'context': __dirname,
27 | entry: {
28 | 'Main': 'src/FeatureTest',
29 | },
30 | output: {
31 | filename: './build/[name].js',
32 | chunkFilename: './build/[id].js',
33 | sourceMapFilename : '[file].map',
34 | },
35 | resolve: {
36 | root: __dirname,
37 | modulesDirectories : ['../third-party/Tone.js/', 'node_modules/tone', 'node_modules', 'src', 'third_party'],
38 | },
39 | plugins: PROD ? [
40 | new webpack.optimize.UglifyJsPlugin({minimize: true})
41 | ] : [],
42 | module: {
43 | loaders: [
44 | {
45 | test: /\.css$/,
46 | loader: 'style-loader!css-loader!postcss-loader'
47 | },
48 | {
49 | test: /\.json$/,
50 | loader: 'json-loader'
51 | },
52 | {
53 | test: /\.js$/,
54 | exclude: /(node_modules|Tone\.js)/,
55 | loader: 'babel', // 'babel-loader' is also a legal name to reference
56 | query: {
57 | presets: ['es2015']
58 | }
59 | },
60 | {
61 | test: /\.(png|gif|jpg|svg)$/,
62 | loader: 'url-loader',
63 | },
64 | {
65 | test : /\.(ttf|eot|woff(2)?)(\?[a-z0-9]+)?$/,
66 | loader : 'file-loader?name=images/font/[hash].[ext]'
67 | // loader : 'file-loader'
68 | }
69 | ]
70 | },
71 | postcss: function () {
72 | return [precss, autoprefixer, calc];
73 | },
74 | devtool: PROD ? '' : '#eval-source-map'
75 | };
--------------------------------------------------------------------------------
/static/src/music/Position.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Config from 'Config'
18 | let position = Config.startingPosition
19 |
20 | const LoopPosition = [{
21 | start : 0,
22 | loopStart : '10m',
23 | loopEnd : '18m',
24 | },
25 | {
26 | start : '10m',
27 | loopStart : '10m',
28 | loopEnd : '18m',
29 | },
30 | {
31 | start : '18m',
32 | loopStart : '18m',
33 | loopEnd : '26m',
34 | },
35 | {
36 | start : '42m',
37 | loopStart : '42m',
38 | loopEnd : '50m',
39 | },
40 | {
41 | start : '42m',
42 | loopStart : '42m',
43 | loopEnd : '50m',
44 | },
45 | {
46 | start : '50m',
47 | loopStart : '50m',
48 | loopEnd : '58m',
49 | }]
50 |
51 | const FillIndices = [0, 0, 1, 1, 2, 2]
52 |
53 | const FillerTextIndices = [0, 1, 2, 3, 4, 5]
54 |
55 | class MusicPosition {
56 |
57 | get position(){
58 | return position
59 | }
60 | set position(val){
61 | position = val
62 | }
63 |
64 | get max(){
65 | return LoopPosition.length
66 | }
67 |
68 | get progress(){
69 | return position / (this.max - 1)
70 | }
71 |
72 | get backgroundLoop(){
73 | return LoopPosition[position]
74 | }
75 |
76 | get fillPosition(){
77 | return FillIndices[position]
78 | }
79 |
80 | get fillerTextPosition(){
81 | return FillerTextIndices[position]
82 | }
83 |
84 | get giorgioBreak(){
85 | return false
86 | // return position === 5
87 | }
88 |
89 | get end(){
90 | return position === (this.max - 1)
91 | }
92 |
93 | get quantizationTime(){
94 | if (position >= 3){
95 | return '@1m'
96 | } else {
97 | return '@4n'
98 | }
99 | }
100 | }
101 |
102 | export default new MusicPosition
--------------------------------------------------------------------------------
/static/src/voice/SpeakBuffer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Buffer from 'Tone/core/Buffer'
18 | import BufferSource from 'Tone/source/BufferSource'
19 | import Text from 'voice/Text'
20 | import VoiceEffects from 'voice/VoiceEffects'
21 | import Time from 'Tone/type/Time'
22 | import Master from 'Tone/core/Master'
23 | import Tone from 'Tone/core/Tone'
24 |
25 | const currentPlaying = []
26 |
27 | function play(text, buffer, time, effect='dry', label){
28 | let source;
29 | return new Promise( (done) => {
30 | if (time instanceof Time){
31 | time = time.toSeconds()
32 | }
33 |
34 | source = new BufferSource(buffer)
35 | currentPlaying.push(source)
36 | source.fadeIn = 0.01
37 | source.fadeOut = 0.01
38 | let duration = buffer.duration
39 | if (effect === 'line' || effect === 'end'){
40 | let landTime = time + Time('4n').mult(4.67).toSeconds()
41 | let startTime = landTime - buffer.duration
42 | VoiceEffects[effect](source, time + Time('1m').toSeconds())
43 | time = startTime
44 | if (time < Tone.now()){
45 | time = Tone.now()
46 | }
47 | if (effect === 'end'){
48 | duration *= 1.3
49 | }
50 | } else if (VoiceEffects.hasOwnProperty(effect)){
51 | VoiceEffects[effect](source)
52 | }
53 | source.start(time)
54 | source.onended = done
55 | Text.show(text, time, duration, label)
56 | }).then(() => {
57 | currentPlaying.splice(currentPlaying.indexOf(source), 1)
58 | })
59 | }
60 |
61 | function stop(time){
62 | currentPlaying.forEach((src) => {
63 | src.stop(time)
64 | })
65 | Text.clear()
66 | }
67 |
68 | function getBuffer(line, rate=1, pitch=0){
69 | return new Promise((done, err) => {
70 | const buff = new Buffer(`speak/?text=${encodeURIComponent(line)}&rate=${rate}&pitch=${pitch}`, () => {
71 | done(buff)
72 | }, err)
73 | })
74 | }
75 |
76 | export default {
77 | play, getBuffer, stop
78 | }
--------------------------------------------------------------------------------
/static/style/shutter.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #flash{
18 | width: 100%;
19 | height: 100%;
20 | position: absolute;
21 | left: 0px;
22 | top: 0px;
23 | color: white;
24 | background-color: white;
25 | opacity: 0;
26 | transition: opacity 0.6s;
27 | pointer-events: none;
28 |
29 | &.visible {
30 | opacity: 1;
31 | transition: opacity 0.1s;
32 | }
33 | }
34 |
35 | $shutterSize : 80px;
36 | $slowTransition: 0.4s;
37 | $fastTransition: 0.25s;
38 |
39 | #shutterButton {
40 | position: absolute;
41 | bottom: 20px;
42 | left: 50%;
43 | width: $shutterSize;
44 | height: $shutterSize;
45 | margin-left: calc(-$shutterSize / 2);
46 | // border-radius: 50%;
47 | // border: 2px solid white;
48 | background-color: transparent;
49 | transition: transform $slowTransition;
50 | transform: scale(0);
51 | pointer-events: none;
52 | user-select: none;
53 | background-image: url(../images/shutter.png);
54 | background-size: 100% 100%;
55 | z-index: 100001;
56 | -webkit-tap-highlight-color: rgba(0,0,0,0);
57 |
58 | $innerSize : calc($shutterSize * 0.8);
59 |
60 | /* #innerShutter {
61 | position: absolute;
62 | width: $innerSize;
63 | height: $innerSize;
64 | left: 50%;
65 | top: 50%;
66 | margin-left: calc(-$innerSize / 2);
67 | margin-top: calc(-$innerSize / 2);
68 | background-color: white;
69 | opacity: 0.7;
70 | border-radius: 50%;
71 | transform: scale(0);
72 | transition: transform $fastTransition;
73 | } */
74 |
75 |
76 | &.visible {
77 | pointer-events: initial;
78 | transform: scale(1);
79 | cursor: pointer;
80 | transition-duration: $fastTransition;
81 |
82 | &:hover {
83 | transform: scale(1.1);
84 | transition-duration: $fastTransition;
85 |
86 | &:active {
87 | filter: brightness(40%);
88 | }
89 | }
90 | /* #innerShutter {
91 | transition-duration: $slowTransition;
92 | transform: scale(1);
93 | } */
94 | }
95 | }
--------------------------------------------------------------------------------
/static/style/camera.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | canvas#three {
18 | opacity : 0;
19 |
20 | &.visible {
21 | opacity: 1;
22 | }
23 | }
24 |
25 | #videoContainer {
26 | position: absolute;
27 | top: 0px;
28 | left: 0px;
29 | height: 100%;
30 | width: 100%;
31 | overflow: hidden;
32 | opacity: 0;
33 | transition: opacity 0.3s;
34 |
35 | &.visible {
36 | opacity: 1;
37 | }
38 |
39 | video {
40 | position: absolute;
41 | top: 50%;
42 | left: 50%;
43 | z-index: 0;
44 | transform: translate(-50%, -50%) scale(0.01);
45 |
46 | &.tall {
47 | height: auto;
48 | width: 100%;
49 | }
50 |
51 | &.wide {
52 | height: 100%;
53 | width: auto;
54 | }
55 |
56 | &.active {
57 | transform: translate(-50%, -50%) scale(1);
58 | }
59 | }
60 |
61 | }
62 |
63 | $maxBounce : 0.3;
64 |
65 | @keyframes bounceAnimation {
66 | 0% {
67 | transform: scale(calc($maxBounce * 0 + 1));
68 | }
69 | 10% {
70 | transform: scale(calc($maxBounce * 0.3 + 1));
71 | }
72 | 20% {
73 | transform: scale(calc($maxBounce * 0.58 + 1));
74 | }
75 | 30% {
76 | transform: scale(calc($maxBounce * 0.8 + 1));
77 | }
78 | 40% {
79 | transform: scale(calc($maxBounce * 0.95 + 1));
80 | }
81 | 50% {
82 | transform: scale(calc($maxBounce * 1 + 1));
83 | }
84 | 60% {
85 | transform: scale(calc($maxBounce * 0.95 + 1));
86 | }
87 | 70% {
88 | transform: scale(calc($maxBounce * 0.8 + 1));
89 | }
90 | 80% {
91 | transform: scale(calc($maxBounce * 0.58 + 1));
92 | }
93 | 90% {
94 | transform: scale(calc($maxBounce * 0.3 + 1));
95 | }
96 | 100% {
97 | transform: scale(calc($maxBounce * 0 + 1));
98 |
99 | }
100 | }
101 |
102 | #image {
103 | position: absolute;
104 | top: 0px;
105 | left: 0px;
106 | width: 100%;
107 | height: 100%;
108 | background-color: blue;
109 | pointer-events: none;
110 | opacity: 0;
111 |
112 | &.bounce {
113 | animation: bounceAnimation 0.46875s infinite;
114 | }
115 |
116 | &.visible {
117 | opacity: 1;
118 | }
119 | }
--------------------------------------------------------------------------------
/static/src/music/Background.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Player from 'Tone/source/Player'
18 | import Config from 'Config'
19 | import Time from 'Tone/type/Time'
20 | import MusicPosition from 'music/Position'
21 | import Transport from 'Tone/core/Transport'
22 |
23 | const backgroundVolume = -8
24 |
25 | export default class BackgroundMusic {
26 | constructor(){
27 | this._player = new Player('../audio/racer.mp3').toMaster()
28 | this._player.volume.value = backgroundVolume
29 |
30 | const startLoop = MusicPosition.backgroundLoop
31 | // set it to loop
32 | this._player.loopStart = startLoop.loopStart
33 | this._player.loopEnd = startLoop.loopEnd
34 | this._player.loop = true
35 |
36 | //the outro music
37 | this._outro = new Player('../audio/end.mp3').toMaster()
38 | this._outro.volume.value = -2
39 |
40 | this._applause = new Player('../audio/applause.mp3').toMaster()
41 | this._applause.volume.value = -4
42 |
43 | this._ended = false
44 |
45 | }
46 |
47 | start(time){
48 | this._player.start(time, MusicPosition.backgroundLoop.start)
49 | }
50 |
51 | fill(){
52 | // duck the volume out immediately
53 | let stopTime = Time(`@${Config.quantizeLevel}`).toSeconds()
54 | this._player.stop(`${stopTime} + 4n`)
55 | this._player.volume.rampTo(-Infinity, '4n', stopTime)
56 | }
57 |
58 | fillEnd(){
59 | if (!this._ended){
60 | let startTime = Time(`@${Config.quantizeLevel}`).toSeconds()
61 | const loopPosition = MusicPosition.backgroundLoop
62 | // set it to loop
63 | this._player.loopStart = loopPosition.loopStart
64 | this._player.loopEnd = loopPosition.loopEnd
65 | this._player.start(startTime, loopPosition.start)
66 | this._player.volume.rampTo(backgroundVolume, 0.01, startTime)
67 | }
68 | }
69 |
70 | stop(){
71 | this._ended = true
72 | const outTime = Time('@4n')
73 | this._outro.start(outTime, 0)
74 | this._outro.volume.rampTo(-Infinity, '4m', outTime)
75 | this._player.volume.rampTo(-Infinity, '2n')
76 | }
77 |
78 | end(){
79 | this._ended = true
80 | this._player.volume.rampTo(-Infinity, Config.fadeOutTime)
81 | this._applause.start()
82 | }
83 | }
--------------------------------------------------------------------------------
/server/mary.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2016 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | import webapp2
19 | import urllib2
20 |
21 | from google.appengine.api import memcache
22 |
23 | MARY_TTS_URL = 'http://127.0.0.1'
24 | MARY_TTS_PORT = '59125'
25 |
26 |
27 | class SpeakHandler(webapp2.RequestHandler):
28 | def maryRequestUrl(self, text, rate, pitch):
29 | f0_scale = '0.8' if float(pitch) == 0 else '1.5'
30 | tract_scaler = '0.9'
31 | f0_add = str(float(pitch) * 10)
32 | text = urllib2.quote(text.encode("utf-8"))
33 | urlString = '{0}:{1}/process?INPUT_TYPE=TEXT&OUTPUT_TYPE=AUDIO&INPUT_TEXT={2}'.format(MARY_TTS_URL, MARY_TTS_PORT, text)
34 | urlString += '&VOICE_SELECTIONS=cmu-bdl-hsmm%20en_US%20male%20hmm&AUDIO_OUT=WAVE_FILE&LOCALE=en_US&VOICE=cmu-bdl-hsmm&AUDIO=WAVE_FILE'
35 | # some voice parameters
36 | urlString += '&effect_F0Scale_selected=on&effect_F0Scale_parameters=f0Scale%3A{0}%3B'.format(f0_scale)
37 | urlString += '&effect_TractScaler_selected=on&effect_TractScaler_parameters=amount%3A{0}%3B'.format(tract_scaler)
38 | urlString += '&effect_F0Add_selected=on&effect_F0Add_parameters=f0Add%3A{0}%3B'.format(f0_add)
39 | urlString += '&effect_Rate_selected=on&effect_Rate_parameters=durScale%3A{0}%3B'.format(str(1 / float(rate)))
40 | return urlString
41 | def get(self):
42 | text = self.request.get('text', default_value='this is a test')
43 | rate = self.request.get('rate', default_value='1')
44 | pitch = self.request.get('pitch', default_value='0')
45 |
46 | key = 'text={0} rate={1} pitch={2}'.format(text, rate, pitch)
47 | print self.maryRequestUrl(text, rate, pitch)
48 | audio = memcache.get(key)
49 | if audio is None:
50 | url = self.maryRequestUrl(text, rate, pitch)
51 | audio = urllib2.urlopen(url).read()
52 | four_hours = 60 * 60 * 4
53 | memcache.add(key, audio, four_hours)
54 | self.response.headers['Content-Type'] = 'audio/wav'
55 | self.response.out.write(audio)
56 |
57 |
58 | app = webapp2.WSGIApplication([
59 | ('/.*', SpeakHandler)
60 | ], debug=True)
61 |
--------------------------------------------------------------------------------
/static/style/about.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | @import 'common.css';
18 |
19 | $pink : #ff52b8;
20 |
21 | #about {
22 | background-color: black;
23 | width: 100%;
24 | height: 100%;
25 | z-index: 1000000;
26 | top: 0px;
27 | left: 0px;
28 | position: absolute;
29 | overflow-y: auto;
30 | display: none;
31 |
32 | &.visible {
33 | display: initial;
34 | }
35 |
36 | #close {
37 | @mixin closeButton;
38 | }
39 |
40 | #content {
41 | width: 90%;
42 | margin: 30px auto 30px auto;
43 | min-width: 300px;
44 | max-width: 800px;
45 | height: auto;
46 | position: relative;
47 | color: white;
48 | font-family: 'Pixel Operator', monospace;
49 | position: relative;
50 |
51 | #title {
52 | font-family: 'Pixel Operator Bold', monospace;
53 | font-size: 40px;
54 | line-height: 30px;
55 | margin: 20px 0px 30px 0px;
56 | position: relative;
57 | }
58 |
59 | $smallScreen : 400px;
60 | $mediumScreen : 800px;
61 |
62 | #video {
63 | width: 100%;
64 | cursor: pointer;
65 | position: relative;
66 |
67 | @media (max-width: $smallScreen) {
68 | height: 200px;
69 | }
70 |
71 | @media (min-width: $smallScreen) and (max-width: $mediumScreen) {
72 | height: 300px;
73 | }
74 |
75 | @media (min-width: $mediumScreen){
76 | height: 400px;
77 | }
78 |
79 | iframe {
80 | width: 100%;
81 | height: 100%;
82 | }
83 |
84 | #playButton {
85 | pointer-events: none;
86 | position: absolute;
87 | top: 50%;
88 | left: 50%;
89 | transform: translate(-50%, -50%);
90 | width: 100px;
91 | height: 100px;
92 | background-image: url(../images/play_button.svg);
93 | background-size: 100% 100%;
94 | display: none;
95 |
96 | &.visible {
97 | display: initial;
98 | }
99 | }
100 |
101 | &:hover #playButton svg{
102 | fill: blue;
103 | }
104 | }
105 |
106 | #blurb {
107 |
108 | position: relative;
109 | text-transform: none;
110 | margin-top: 30px;
111 | font-size: 20px;
112 | line-height: 25px;
113 |
114 | a {
115 | @mixin yellowLink;
116 | color: $pink;
117 | }
118 | }
119 |
120 | }
121 | }
--------------------------------------------------------------------------------
/static/src/voice/Filler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import SpeakBuffer from 'voice/SpeakBuffer'
18 | import Loop from 'Tone/event/Loop'
19 | import Time from 'Tone/type/Time'
20 | import Lines from 'voice/Lines'
21 | import MusicPosition from 'music/Position'
22 |
23 | //special thanks to giorgio moroder, here he is on the vocoder
24 |
25 | const fillerPhrases = [
26 | ['Snap a photo', 'I am ready', 'Waiting for you'],
27 | ['Okay what else?', 'Turn it up', 'Yeah', 'Snap another', 'Image Recognition, yeah'],
28 | ['Turn it up', 'Speech Synthesis, yeah', 'Don\'t stop', 'This is Awesome'],
29 | ['Woah', 'Keep it going', 'Take another', 'This is Crazy'],
30 | ['One more time', 'Last one', 'Alright', 'Yeah'],
31 | ['That was awesome', 'I am shutting down'],
32 | ]
33 |
34 | export default class Filler {
35 | constructor(){
36 |
37 | this._loop = new Loop(this._speak.bind(this), '2m')
38 |
39 | this._phraseIndex = -1
40 |
41 | this._phrases = []
42 |
43 | fillerPhrases.forEach((phrases, i) => {
44 | this._phrases[i] = []
45 | phrases.forEach((text, j) => {
46 | SpeakBuffer.getBuffer(text, 1.3, 2).then( (buffer) => {
47 | this._phrases[i][j] = {text, buffer}
48 | })
49 | })
50 | })
51 | }
52 |
53 | start(){
54 | if (this._loop.state === 'stopped'){
55 | this._phraseIndex = 0
56 | if (MusicPosition.end){
57 | this._loop.interval = '1m'
58 | }
59 | if (MusicPosition.position === 0){
60 | this._loop.start("@1m + 1m")
61 | } else {
62 | this._loop.start("@1m")
63 | }
64 | }
65 | }
66 |
67 | stop(){
68 | this._loop.stop()
69 | }
70 |
71 | _chooseRandom(){
72 | //randomness excludes the first result
73 | const random = Math.floor(Math.random() * (this._phrases[MusicPosition.fillerTextPosition].length - 1)) + 1
74 | if (random === this._phraseIndex){
75 | return this._chooseRandom()
76 | } else {
77 | return random
78 | }
79 |
80 | }
81 |
82 | _speak(time){
83 | const phrase = this._phrases[MusicPosition.fillerTextPosition][this._phraseIndex]
84 | if (phrase){
85 | SpeakBuffer.play(phrase.text, phrase.buffer, Time(time), 'fill')
86 | if (MusicPosition.end){
87 | this._phraseIndex++
88 | this._loop.stop('+2m + 4n')
89 | } else {
90 | this._phraseIndex = this._chooseRandom()
91 | }
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/server/vision.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2016 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 |
18 | import webapp2
19 | import urllib2
20 | import json
21 |
22 | import base64
23 |
24 | from google.appengine.ext import vendor
25 | vendor.add('server/lib')
26 |
27 | import re
28 | from googleapiclient import discovery
29 | from oauth2client.client import GoogleCredentials
30 |
31 | # remove these things cause it could be taken the wrong way
32 | DISCOVERY_URL='https://{api}.googleapis.com/$discovery/rest?version={apiVersion}'
33 |
34 | class VisionApi(webapp2.RequestHandler):
35 | def __init__(self, request, response):
36 | # Set self.request, self.response and self.app.
37 | self.initialize(request, response)
38 | self.vision = self._create_client()
39 |
40 | def _create_client(self):
41 | credentials = GoogleCredentials.get_application_default()
42 | return discovery.build(
43 | 'vision', 'v1', credentials=credentials,
44 | discoveryServiceUrl=DISCOVERY_URL)
45 |
46 | def _label(self, image, max_results=10, num_retries=2):
47 | """
48 | Uses the Vision API to detect text in the given file.
49 | """
50 |
51 | label_request = {
52 | 'image': {
53 | 'content': image
54 | },
55 | 'features': [{
56 | 'type': 'LABEL_DETECTION',
57 | 'maxResults': max_results,
58 | }]
59 | }
60 |
61 | request = self.vision.images().annotate(body={'requests': [label_request]})
62 | response = request.execute(num_retries=num_retries)['responses'][0]
63 | labels_raw = response.get('labelAnnotations', [])
64 | print labels_raw
65 | error = response.get('error', [])
66 | if len(labels_raw):
67 | filtered = [r for r in labels_raw if float(r['score']) > 0.1]
68 | labels = [{'label' : str(x['description']), 'score' : float(x['score'])} for x in filtered]
69 | return labels
70 | elif len(error):
71 | return {'error' : error}
72 | else:
73 | return []
74 |
75 |
76 | def post(self):
77 | image = self.request.POST.multi['image']
78 | labels = self._label(image)
79 | self.response.out.write(json.dumps(labels))
80 |
81 |
82 | app = webapp2.WSGIApplication([
83 | ('.*', VisionApi)
84 | ], debug=True)
--------------------------------------------------------------------------------
/static/src/voice/Lyrics.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import MusicPosition from 'music/Position'
18 |
19 | const couplets = [
20 | ['Is that % I see?', 'Looks like % to me'],
21 | ['Is that % or not?', 'Got % in this spot'],
22 | // ['%, that\'s what I say', 'Looks like % to me'],
23 | // ['I see %, right?', 'Got % in my sight'],
24 | ['That\'s % on my screen', 'I think % is what I\'ve seen'],
25 | ['Looks like % to me', 'I\'m seeing %, probably'],
26 | ['I think % is what we\'ve got', 'Could be %, but maybe not'],
27 | ]
28 |
29 | const confused = [
30 | ['What is that i see?', 'This image is confusing me'],
31 | // ['Take another pic, okay?', 'Rocking Google Cloud Vision all day'],
32 | ['I D K', 'Take another pic, okay?'],
33 | ['I\'m not quite sure what we\'ve got', 'Please take another shot']
34 | ]
35 |
36 | let history = []
37 | let lastLineIndex = -1
38 |
39 | /**
40 | * Get a random line which includes the labels.
41 | * Make sure it has a different index than the last one.
42 | */
43 | function randomLine(labels){
44 | let randIndex = Math.floor(Math.random() * couplets.length)
45 | //make sure you don't repeat the line
46 | if (randIndex === lastLineIndex){
47 | return randomLine(labels)
48 | }
49 | lastLineIndex = randIndex
50 | let randLine = couplets[randIndex]
51 | randLine = randLine.slice()
52 | randLine[0] = randLine[0].replace(/%/g, labels[0].label)
53 | randLine[1] = randLine[1].replace(/%/g, labels[1].label)
54 | return randLine
55 | }
56 |
57 | /**
58 | * Given the array of labels in this form:
59 | * [ {
60 | * label : 'label text',
61 | * score : Number
62 | * }, ...]
63 | * Returns two lines which includes one of those labels and the labels
64 | * that it used
65 | * @returns {Object} {lines : [], labels : []}
66 | */
67 | function getLine(labels){
68 | if (labels.length > 1){
69 | //choose a label that haven't been used in a while
70 | //reverse sort the labels by their position in the history
71 | labels = labels.sort((a, b) => {
72 | let aIndex = history.findIndex((l) => l.label === a.label) + 1
73 | let bIndex = history.findIndex((l) => l.label === b.label) + 1
74 | return aIndex - bIndex
75 | })
76 | let retLabels = labels.slice(0, 2)
77 | history = history.concat(retLabels)
78 | // put it in the line
79 | return { lines : randomLine(retLabels), labels : retLabels}
80 | } else {
81 | let randLine = confused[Math.floor(Math.random() * confused.length)]
82 | if (MusicPosition.end){
83 | //doesn't make sense to say take another shot at the very end
84 | randLine = confused[0]
85 | }
86 | return {lines: randLine.slice(), labels : []}
87 | }
88 | }
89 |
90 |
91 | export default {getLine}
--------------------------------------------------------------------------------
/static/src/voice/VoiceEffects.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Convolver from 'Tone/effect/Convolver'
18 | import Compressor from 'Tone/component/Compressor'
19 | import PingPongDelay from 'Tone/effect/PingPongDelay'
20 | import FeedbackDelay from 'Tone/effect/FeedbackDelay'
21 | import PitchShift from 'Tone/effect/PitchShift'
22 | import Gain from 'Tone/core/Gain'
23 | import Tone from 'Tone/core/Tone'
24 |
25 | //a master out object so that they can all be stopped at once
26 | const VoiceMasterOut = new Gain().toMaster()
27 |
28 | const delay = new PingPongDelay('4n', 0.4).connect(VoiceMasterOut)
29 | delay.wet.value = 0.8
30 |
31 | const delayInput = new Gain().connect(delay)
32 | delayInput.gain.value = 0
33 |
34 | const comp = new Compressor().connect(VoiceMasterOut)
35 | comp.connect(delayInput)
36 |
37 | const fillDelay = new FeedbackDelay({
38 | delayTime : '4n',
39 | feedback : 0.25,
40 | wet : 0.3,
41 | }).connect(VoiceMasterOut)
42 |
43 | const reverseVerb = new Convolver({
44 | url : 'audio/reverseCrash.mp3',
45 | wet : 1
46 | }).connect(fillDelay)
47 |
48 |
49 | export default {
50 | dry : function(source){
51 | source.connect(VoiceMasterOut)
52 | },
53 |
54 | fill : function(source){
55 | source.connect(VoiceMasterOut)
56 | // source.connect(fillDelay)
57 | source.connect(reverseVerb)
58 | },
59 |
60 | wait : function(source){
61 | source.connect(VoiceMasterOut)
62 | // source.connect(fillDelay)
63 | source.connect(reverseVerb)
64 | },
65 |
66 | line : function(source, effectTime){
67 | source.connect(comp)
68 | this._delay(effectTime)
69 | },
70 |
71 | down : function(source, effectTime){
72 | let rampTime = source.buffer.duration * 0.25
73 | source.playbackRate.exponentialRampToValue(0.7, rampTime, Tone.now() + source.buffer.duration * 0.75)
74 | source.toMaster()
75 | source.connect(reverseVerb)
76 | },
77 |
78 | end : function(source, effectTime){
79 | const pitchDelay = new PitchShift({
80 | delayTime : '4n',
81 | feedback : 0.4,
82 | wet : 0,
83 | pitch : -2,
84 | windowSize : 0.05
85 | }).toMaster()
86 | let rampTime = source.buffer.duration * 0.25
87 | pitchDelay.wet.setValueAtTime(0.5, effectTime - rampTime / 2)
88 | source.connect(pitchDelay)
89 | source.playbackRate.exponentialRampToValue(0.7, rampTime, effectTime - rampTime)
90 | source.toMaster()
91 | },
92 |
93 | _delay : function(time){
94 | delayInput.gain.setValueAtTime(1, time)
95 | delayInput.gain.linearRampToValueAtTime(0, time + 2)
96 | },
97 |
98 | stop : function(){
99 | VoiceMasterOut.gain.rampTo(0, 0.8)
100 | }
101 | }
--------------------------------------------------------------------------------
/static/src/interface/Splash.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import 'style/splash.css'
18 | import events from 'events'
19 | const EventEmitter = events.EventEmitter
20 | import Loader from 'interface/Loader'
21 | import Config from 'Config'
22 | import screenfull from 'screenfull'
23 | import Title from 'interface/Title'
24 |
25 |
26 | const TitleColors = ['pink', 'purple', 'yellow', 'teal', 'purple', 'yellow', 'teal', 'space', 'pink', 'yellow', 'teal']
27 |
28 | export default class Splash extends EventEmitter{
29 | constructor(container){
30 | super()
31 |
32 | this._splashElement = document.createElement('div')
33 | this._splashElement.id = 'splash'
34 | container.appendChild(this._splashElement)
35 |
36 | // the title
37 | const titleContainer = document.createElement('div')
38 | titleContainer.id = 'titleContainer'
39 | this._splashElement.appendChild(titleContainer)
40 |
41 | const title = new Title(titleContainer)
42 |
43 | const subTitle = document.createElement('div')
44 | subTitle.id = 'subTitle'
45 | titleContainer.appendChild(subTitle)
46 | subTitle.textContent = 'Use your camera to make music with me.'
47 |
48 | // the loader / ready button
49 | this._loader = new Loader(titleContainer)
50 |
51 | this._loader.on('start', () => {
52 |
53 | this.emit('start')
54 | this._splashElement.classList.add('disappear')
55 |
56 | if (screenfull.enabled && Config.fullscreen) {
57 | screenfull.request()
58 | }
59 | })
60 |
61 | const learnMore = document.createElement('a')
62 | learnMore.id = 'learnMore'
63 | learnMore.textContent = 'About'
64 | titleContainer.appendChild(learnMore)
65 | learnMore.addEventListener('click', (e) => {
66 | e.preventDefault()
67 | this.emit('about')
68 | })
69 |
70 | // logos at the bottom
71 | const aiExperiments = document.createElement('a')
72 | aiExperiments.id = 'aiExperiments'
73 | aiExperiments.href = Config.aiExperimentsLink
74 | aiExperiments.target = '_blank'
75 | this._splashElement.appendChild(aiExperiments)
76 |
77 | // break
78 | const badgeBreak = document.createElement('div')
79 | badgeBreak.id = 'badgeBreak'
80 | this._splashElement.appendChild(badgeBreak)
81 |
82 | const googleFriends = document.createElement('a')
83 | googleFriends.id = 'googleFriends'
84 | this._splashElement.appendChild(googleFriends)
85 |
86 | const privacyAndTerms = document.createElement('div')
87 | privacyAndTerms.id = 'privacyAndTerms'
88 | privacyAndTerms.innerHTML = 'privacy&terms'
89 | this._splashElement.appendChild(privacyAndTerms)
90 |
91 | }
92 |
93 | show(){
94 | this._splashElement.classList.remove('disappear')
95 | }
96 |
97 | }
--------------------------------------------------------------------------------
/static/src/interface/About.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import 'style/about.css'
18 | import Config from 'Config'
19 | import YouTubeIframeLoader from 'youtube-iframe'
20 |
21 | const maryLink = 'https://github.com/marytts/marytts'
22 | const cloudVisionLink = 'https://cloud.google.com/vision/'
23 | const sourceCode = 'https://github.com/googlecreativelab/aiexperiments-giorgio-cam'
24 |
25 | const blurbCopy = `Built by Eric Rosenbaum, Yotam Mann, and friends at Google Creative Lab
26 | using Google Cloud Vision API and MaryTTS.
27 | The open-source code is available here. Check out more
28 | A.I. Experiments.`
29 |
30 | export default class About {
31 | constructor(container){
32 |
33 | this._container = document.createElement('div')
34 | this._container.id = 'about'
35 | container.appendChild(this._container)
36 |
37 | const closeButton = document.createElement('div')
38 | closeButton.id = 'close'
39 | closeButton.classList.add('visible')
40 | this._container.appendChild(closeButton)
41 | closeButton.addEventListener('click', (e) => {
42 | e.preventDefault()
43 | this.close()
44 | })
45 |
46 | const content = document.createElement('div')
47 | content.id = 'content'
48 | this._container.appendChild(content)
49 |
50 | const title = document.createElement('div')
51 | title.id = 'title'
52 | title.textContent = Config.name
53 | content.appendChild(title)
54 |
55 |
56 | const video = document.createElement('div')
57 | video.id = 'video'
58 | //vid YT0k99hCY5I
59 | video.innerHTML = ``
60 | content.appendChild(video)
61 |
62 | this._ytplayer = null
63 |
64 | this._playButton = document.createElement('div')
65 | this._playButton.id = 'playButton'
66 | this._playButton.classList.add('visible')
67 | video.appendChild(this._playButton)
68 |
69 | YouTubeIframeLoader.load((YT) => {
70 | this._ytplayer = new YT.Player('youtube-iframe', {
71 | events : {
72 | onStateChange : (state) => {
73 | this._playButton.classList.remove('visible')
74 | }
75 | }
76 | })
77 | })
78 |
79 | const blurb = document.createElement('div')
80 | blurb.id = 'blurb'
81 | content.appendChild(blurb)
82 | blurb.innerHTML = blurbCopy
83 |
84 | }
85 | close(){
86 | this._container.classList.remove('visible')
87 | if (this._ytplayer){
88 | this._ytplayer.stopVideo()
89 | }
90 | if (ga){
91 | ga('send', 'event', 'GiorgioCam', 'Click', 'About - Close')
92 | }
93 | }
94 | open(){
95 | this._playButton.classList.add('visible')
96 | this._container.classList.add('visible')
97 | if (ga){
98 | ga('send', 'event', 'GiorgioCam', 'Click', 'About - Open')
99 | }
100 | }
101 | }
--------------------------------------------------------------------------------
/static/src/voice/Wait.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import SpeakBuffer from 'voice/SpeakBuffer'
18 | import Loop from 'Tone/event/Loop'
19 | import Time from 'Tone/type/Time'
20 | import AudioBuffer from 'Tone/core/Buffer'
21 | import MusicPosition from 'music/Position'
22 | import Player from 'Tone/source/Player'
23 | import Sequence from 'Tone/event/Sequence'
24 | import Config from 'Config'
25 |
26 | const openingLines = [
27 | {text : 'H h here we go', url : 'herewego.mp3'},
28 | {text : 'y y y-y yeah' , url : 'yeah.mp3'},
29 | {text : 'a alright', url : 'alright.mp3'},
30 | {text : 'yeah yeah yeah yeah yeah-yeah yeah-yeah yeah-yeah yeah-yeah', url : 'yeahup.mp3'},
31 | {text : 'here we go go go go go-go go-go go-go go-go', url : 'herewegogo.mp3'},
32 | ]
33 |
34 | const soundEffects = new Player({url : 'audio/siren.mp3', volume : -6}).toMaster()
35 |
36 | const waitingLines = ['I\'m on it', 'Alright', 'Working on it', 'Almost there', 'Working on it', 'Almost there']
37 |
38 | export default class Wait {
39 | constructor(){
40 |
41 | // this._loop = new Loop(this._speak.bind(this), '1m')
42 | // this._loop.mute = true
43 |
44 | this._phraseIndex = 0
45 |
46 | this._phrases = []
47 | openingLines.forEach((desc, i) => {
48 | const text = desc.text
49 | if (desc.url){
50 | const buffer = new AudioBuffer(`audio/voice/${desc.url}`)
51 | this._phrases[i] = {text, buffer}
52 | } else {
53 | SpeakBuffer.getBuffer(text, 1.2, 2.5).then((buffer) => {
54 | this._phrases[i] = {text, buffer}
55 | })
56 | }
57 | })
58 |
59 | waitingLines.forEach((text, i) => {
60 | SpeakBuffer.getBuffer(text, 1.2, 2).then((buffer) => {
61 | waitingLines[i] = {text, buffer}
62 | })
63 | })
64 | this._waitSequence = new Sequence(this._playNextWait.bind(this), [0, 1, 2, 3, 4, 5], '1m')
65 | }
66 |
67 | start(){
68 | let phrase = this._phrases[MusicPosition.position - 1]
69 | SpeakBuffer.play(phrase.text, phrase.buffer, Time('@4n + 4n'), 'wait')
70 | if (MusicPosition.position >= 4){
71 | soundEffects.start('@4n + 4n')
72 | }
73 | this._waitSequence.start('@1m + 1m')
74 | }
75 |
76 | stop(){
77 | if (soundEffects.state === 'started'){
78 | soundEffects.stop(`@${Config.quantizeLevel}`)
79 | }
80 | this._waitSequence.stop()
81 | SpeakBuffer.stop(`@${Config.quantizeLevel}`)
82 | }
83 |
84 | _chooseRandom(){
85 | let randIndex = Math.floor(Math.random() * this._phrases.length)
86 | if (randIndex === this._phraseIndex){
87 | return this._chooseRandom()
88 | } else {
89 | return randIndex
90 | }
91 | }
92 |
93 | _playNextWait(time, index){
94 | const phrase = waitingLines[index]
95 | SpeakBuffer.play(phrase.text, phrase.buffer, Time(time), 'wait')
96 | }
97 |
98 | _speak(time){
99 | //choose a phrase at random
100 | let phrase = this._phrases[this._phraseIndex]
101 | SpeakBuffer.play(phrase.text, phrase.buffer, Time(time), 'wait')
102 | this._phraseIndex = (this._phraseIndex + 1) % this._phrases.length
103 | // this._phraseIndex = this._chooseRandom()
104 | }
105 | }
--------------------------------------------------------------------------------
/static/src/camera/Camera.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import 'webrtc-adapter'
18 | import mobile from 'is-mobile'
19 | import 'style/camera.css'
20 | import Label from 'camera/Label'
21 | import Config from 'Config'
22 | import Canvas from 'camera/Canvas'
23 |
24 | export default class Camera {
25 | constructor(container) {
26 |
27 | this._canvas = new Canvas(container)
28 |
29 | this._video = document.createElement('video')
30 | window.video = this._video
31 |
32 | this._constraints = {
33 | audio: false,
34 | video: {
35 | facingMode : { ideal: 'environment' }
36 | }
37 | }
38 |
39 | this._frontFacing = !mobile()
40 | }
41 |
42 | open(){
43 |
44 | let tries = 0
45 | return navigator.mediaDevices.getUserMedia(this._constraints).then((stream) => {
46 |
47 | window.stream = stream
48 |
49 | // stream.
50 | this._video.srcObject = stream
51 | this._video.autoplay = true
52 |
53 | this._video.classList.add('visible')
54 | this._canvas.setVideo(this._video)
55 |
56 | window.addEventListener('resize', this._resize.bind(this))
57 | //size it initially
58 | this._resize()
59 | })
60 | }
61 |
62 | error(){
63 | this.close()
64 | this._canvas.fail()
65 | }
66 |
67 | end(){
68 | return this._canvas.fail(Config.fadeOutTime * 1000)
69 | }
70 |
71 | close(){
72 | this._video.pause()
73 | return this._canvas.fail().then( () => {
74 | this._video.srcObject.getVideoTracks()[0].stop()
75 | })
76 | }
77 |
78 | _getImage(){
79 | const video = this._video
80 | if (video){
81 | const w = video.videoWidth
82 | const h = video.videoHeight
83 | const canvas = document.createElement('canvas')
84 | const context = canvas.getContext('2d')
85 | // take a snapshot of the incoming image
86 | canvas.width = w
87 | canvas.height = h
88 | context.drawImage(video, 0, 0, w, h)
89 | return canvas
90 | }
91 | }
92 |
93 | label() {
94 | return Label(this._getImage())
95 | }
96 |
97 | /**
98 | * Set the sizing of the video box
99 | */
100 | _resize(){
101 | let aspectRatio = this._video.videoWidth / this._video.videoHeight
102 | if (isNaN(aspectRatio)){
103 | setTimeout(() => this._resize(), 100)
104 | } else {
105 | let windowRatio = window.innerWidth / window.innerHeight
106 | this._video.classList.add('active')
107 | if (aspectRatio > windowRatio){
108 | this._video.classList.add('wide')
109 | this._video.classList.remove('tall')
110 | } else {
111 | this._video.classList.add('tall')
112 | this._video.classList.remove('wide')
113 | }
114 | this._canvas.resize(this._video.videoWidth, this._video.videoHeight)
115 |
116 | if (this._frontFacing){
117 | this._canvas.mirror()
118 | }
119 | }
120 | }
121 |
122 | takePicture(){
123 | this._video.pause()
124 | this._canvas.changeColor()
125 | }
126 |
127 | resume(){
128 | this._canvas.endBounce()
129 | this._canvas.endChangeColor()
130 | const promise = this._video.play()
131 | if (promise){
132 | return promise
133 | } else {
134 | return Promise.resolve()
135 | }
136 | }
137 |
138 | bounce(){
139 | this._canvas.bounce()
140 | }
141 |
142 | changeColor(){
143 | this._canvas.changeColor()
144 | }
145 |
146 | giorgioBreak(){
147 | this._canvas.changeColor()
148 | }
149 |
150 | }
--------------------------------------------------------------------------------
/static/src/Main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import 'style/main.css'
18 | import Splash from 'interface/Splash'
19 | import Camera from 'camera/Camera'
20 | import Shutter from 'interface/Shutter'
21 | import Music from 'music/Music'
22 | import Voice from 'voice/Voice'
23 | import Text from 'voice/Text'
24 | import Config from 'Config'
25 | import Close from 'interface/Close'
26 | import OrientationListener from 'interface/Orientation'
27 | import NoCamera from 'interface/NoCamera'
28 | import About from 'interface/About'
29 | import MusicPosition from 'music/Position'
30 | import WhenVisible from 'Visible'
31 |
32 |
33 | // set the title
34 | document.title = Config.name
35 |
36 | const camera = new Camera(document.body)
37 | const shutter = new Shutter(document.body)
38 | const splash = new Splash(document.body)
39 | const closeButton = new Close(document.body)
40 | const about = new About(document.body)
41 |
42 | const music = new Music()
43 | const voice = new Voice()
44 |
45 |
46 | splash.on('start', () => {
47 |
48 | Text.appendTo(document.body)
49 |
50 | camera.open().then( () => {
51 | closeButton.show()
52 | const time = music.start()
53 | return voice.intro(time)
54 | }).then( () => {
55 | voice.fill()
56 | shutter.show()
57 | }).catch((e) => {
58 | NoCamera(document.body)
59 | })
60 | })
61 |
62 | splash.on('about', () => {
63 | about.open()
64 | })
65 |
66 | closeButton.on('about', () => {
67 | about.open()
68 | })
69 |
70 | closeButton.on('click', () => {
71 | Text.clear()
72 | voice.stop()
73 | music.stop()
74 | camera.close().then( () => {
75 | closeButton.end()
76 | })
77 | shutter.remove()
78 | })
79 |
80 | let retries = 0
81 |
82 | shutter.on('click', () => {
83 |
84 | //increment the position
85 | MusicPosition.position++
86 |
87 | music.fill()
88 | voice.endFill()
89 |
90 | camera.takePicture()
91 |
92 | camera.label().then((labels) => {
93 |
94 | return voice.load(labels)
95 |
96 | }).then((lines) => {
97 | // this is so the music doesn't drop out when the transition happens
98 | // while in a background tab
99 | return WhenVisible(lines)
100 | }).then((lines) => {
101 |
102 | voice.endWait()
103 |
104 | return music.endFill().then( (time) => {
105 | camera.bounce()
106 | return voice.speak(lines, time)
107 | })
108 |
109 | }).then(() => {
110 | voice.fill()
111 | shutter.show()
112 | return camera.resume()
113 | }).then(() => {
114 | //ENDING
115 | if (MusicPosition.end){
116 | Text.clear()
117 | camera.end().then(() => {
118 | camera.close()
119 | closeButton.end()
120 | })
121 | music.end()
122 | closeButton.hide()
123 | shutter.remove()
124 | }
125 | }).catch((err) => {
126 |
127 | WhenVisible().then(() => {
128 | // errorr!!!
129 | console.log(err)
130 |
131 | MusicPosition.position--
132 | Text.clear()
133 | voice.endWait()
134 |
135 | retries++
136 | if (retries > 2){
137 | camera.error()
138 | closeButton.hide()
139 | voice.error().then(() => {
140 | closeButton.end()
141 | })
142 | music.stop()
143 | } else {
144 |
145 | music.endFill().then((time) => {
146 | return voice.tryAgain(time)
147 | }).then( () => {
148 | camera.resume()
149 | shutter.show()
150 | })
151 | }
152 | })
153 | })
154 | })
--------------------------------------------------------------------------------
/static/src/voice/Voice.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Intro from 'voice/Intro'
18 | import Filler from 'voice/Filler'
19 | import Wait from 'voice/Wait'
20 | import Lines from 'voice/Lines'
21 | import SpeakBuffer from 'voice/SpeakBuffer'
22 | import VoiceEffects from 'voice/VoiceEffects'
23 | import Lyrics from 'voice/Lyrics'
24 | import Config from 'Config'
25 | import Transport from 'Tone/core/Transport'
26 | import Time from 'Tone/type/Time'
27 |
28 | export default class Voice {
29 | constructor(){
30 | this._intro = new Intro()
31 |
32 | this._filler = new Filler()
33 |
34 | this._wait = new Wait()
35 | }
36 |
37 | intro(time){
38 | return this._intro.start(time)
39 | }
40 |
41 | fill(){
42 | this._filler.start()
43 | }
44 |
45 | endFill(){
46 | this._filler.stop()
47 | this.wait()
48 | }
49 |
50 | wait(){
51 | this._wait.start()
52 | }
53 |
54 | endWait(){
55 | this._wait.stop()
56 | }
57 |
58 | stop(){
59 | VoiceEffects.stop()
60 | const endLines = Lines.end
61 | return SpeakBuffer.play(endLines[1].text, endLines[1].buffer, Time(), 'end')
62 | }
63 |
64 | //speak the error lines
65 | error(){
66 | const errorLines = Lines.end
67 | return SpeakBuffer.play(errorLines[0].text, errorLines[0].buffer, Time('+0:3'), 'end')
68 | .then(() => {
69 | // add a little time at the end
70 | return new Promise((done) => {
71 | setTimeout(done, 3000)
72 | })
73 | })
74 | }
75 |
76 | tryAgain(time){
77 | const errorLines = Lines.end
78 | return SpeakBuffer.play(errorLines[2].text, errorLines[2].buffer, time, 'line')
79 | .then(() => {
80 | // add a little time at the end
81 | return new Promise((done) => {
82 | setTimeout(done, 600)
83 | })
84 | })
85 | }
86 |
87 | openCamera(){
88 | const errorLines = Lines.end
89 | return SpeakBuffer.play(errorLines[3].text, errorLines[3].buffer, Time('+0.1'), 'dry')
90 | .then(() => {
91 | // add a little time at the end
92 | return new Promise((done) => {
93 | setTimeout(done, 1200)
94 | })
95 | })
96 | }
97 |
98 | /**
99 | * Formats the return promise
100 | * {
101 | * text : 'text here',
102 | * buffer : Tone.Buffer,
103 | * label : {} // label obj
104 | * }
105 | */
106 | _loadLine(text, label){
107 | return SpeakBuffer.getBuffer(text, 1.1, 1).then( (buffer) => {
108 | return { text, label, buffer }
109 | })
110 | }
111 |
112 | /**
113 | * Loads the lines, resolves the promise at the beginning of the next phrase.
114 | * Returns both lines in this format:
115 | * [
116 | * {
117 | * text : 'text here',
118 | * buffer : Tone.Buffer,
119 | * label : {} // label obj
120 | * }
121 | * ]
122 | */
123 | load(labels){
124 | let lyrics = Lyrics.getLine(labels)
125 | return Promise.all([
126 | this._loadLine(lyrics.lines[0], lyrics.labels[0]),
127 | this._loadLine(lyrics.lines[1], lyrics.labels[1])
128 | ])
129 | }
130 |
131 | _speakLine(line, time){
132 | return SpeakBuffer.play(line.text, line.buffer, time, 'line', line.label)
133 | }
134 |
135 | speak(lines, time){
136 | this._speakLine(lines[0], Time(time))
137 | return this._speakLine(lines[1], Time(time).add('2m')).then( () => {
138 | // add a short timeout at the end
139 | return new Promise((done) => {
140 | setTimeout(done, 1400)
141 | })
142 | })
143 | }
144 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Giorgio Cam
2 | Take a picture to make music with the computer.
3 |
4 | ## About
5 |
6 | This is an experiment built with machine learning that lets you make music with the computer just by taking a picture. It uses image recognition to label what it sees, then it turns those labels into lyrics of a song.
7 |
8 | [https://aiexperiments.withgoogle.com/giorgio-cam](https://aiexperiments.withgoogle.com/giorgio-cam)
9 |
10 | This is not an official Google product.
11 |
12 | ## Credits
13 |
14 | Built by [Eric Rosenbaum](https://github.com/ericrosenbaum), [Yotam Mann](https://github.com/tambien), and friends at Google Creative Lab using [MaryTTS](https://github.com/marytts/marytts), [Tone.js](https://github.com/Tonejs/Tone.js), and [Google Cloud Vision API](https://cloud.google.com/vision/). Check out more at [A.I. Experiments] (https://aiexperiments.withgoogle.com).
15 |
16 | ## Overview
17 |
18 | The client-side javascript application captures images using WebRTC. When the user hits the shutter button, an image is sent to the server which then returns an array of labels and confidence scores for that image using Cloud Vision. These labels are dropped into a rhyming template to create the next phrase that the computer will speak. To get the audio of that phrase, the client makes another request to the MaryTTS (text to speech) server which returns a wav file of the audio. That audio is then synced to the music using Tone.js.
19 |
20 | ## FRONT-END
21 |
22 | To build the client-side javascript, first install [node](https://nodejs.org) and [webpack](https://webpack.github.io/). Then you can install of the dependencies of the project by typing the following in the terminal:
23 |
24 | ```bash
25 | cd static
26 | npm install
27 | ```
28 |
29 | Then build all of the files
30 |
31 | ```bash
32 | webpack -p
33 | ```
34 |
35 | ## Back-end
36 |
37 | The back-end uses [Google App Engine](https://cloud.google.com/appengine/) to serve static content and mediate between the two other back-end services: Google Cloud Vision and MaryTTS.
38 |
39 | #### Google Cloud Vision API
40 |
41 | You will need to first enable the API and [generate credentials](https://cloud.google.com/vision/docs/common/auth). Under "Key type", use "JSON" and then download the key.json file.
42 |
43 | Add your json key to the `env_variables` section of your `.yaml` file like so:
44 |
45 | ```yaml
46 | env_variables:
47 | GOOGLE_APPLICATION_CREDENTIALS: PATH/TO/CLOUD_VISION_KEY.json
48 | ```
49 |
50 | #### MaryTTS
51 |
52 | Download and install [MaryTTS](https://github.com/marytts/marytts). Then run the MaryTTS Server. Add the IP Address and Port number that MaryTTS is running on to `server/mary.py`. The default location is `http://localhost:59125`
53 |
54 | ```python
55 | MARY_TTS_URL = 'http://127.0.0.1'
56 | MARY_TTS_PORT = '59125'
57 | ```
58 |
59 | #### App Engine
60 |
61 | To install the dependencies so they can be launched within Google App Engine they will need to be installed into a local folder (documented [here](https://cloud.google.com/appengine/docs/python/tools/using-libraries-python-27)).
62 |
63 | ```bash
64 | cd server
65 | mkdir lib
66 | pip install -t lib -r requirements.txt
67 | ```
68 |
69 | Then [follow instructions](https://cloud.google.com/appengine/docs/python/quickstart) on launching your App Engine code.
70 |
71 | ## Music
72 |
73 | [Racer](https://www.youtube.com/watch?v=YT0k99hCY5I) by [Giorgio Moroder](https://en.wikipedia.org/wiki/Giorgio_Moroder)
74 |
75 |
76 | ## License
77 |
78 | Copyright 2016 Google Inc.
79 |
80 | Licensed under the Apache License, Version 2.0 (the "License");
81 | you may not use this file except in compliance with the License.
82 | You may obtain a copy of the License at
83 |
84 | http://www.apache.org/licenses/LICENSE-2.0
85 |
86 | Unless required by applicable law or agreed to in writing, software
87 | distributed under the License is distributed on an "AS IS" BASIS,
88 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
89 | See the License for the specific language governing permissions and
90 | limitations under the License.
91 |
--------------------------------------------------------------------------------
/static/src/interface/Title.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import 'style/title.css'
18 |
19 | const images = {
20 | rest : 'giorgio_logo_anim__0000_eyes_right.png',
21 | blink : 'giorgio_logo_anim__0001_eyes_right_blink.png',
22 | sing0 : 'giorgio_logo_anim__0002_mouth_open.png',
23 | sing1 : 'giorgio_logo_anim__0004_little_note_2.png',
24 | sing2 : 'giorgio_logo_anim__0005_little_note_3.png',
25 | sing3 : 'giorgio_logo_anim__0006_little_note_4.png',
26 | sing4 : 'giorgio_logo_anim__0007_little_note_5.png',
27 | sing5 : 'giorgio_logo_anim__0008_little_note_6.png',
28 | sing6 : 'giorgio_logo_anim__0009_big_notes_1.png',
29 | sing7 : 'giorgio_logo_anim__0010_big_notes_2.png',
30 | sing8 : 'giorgio_logo_anim__0011_big_notes_3.png',
31 | sing9 : 'giorgio_logo_anim__0012_big_notes_4.png',
32 | sing10 : 'giorgio_logo_anim__0013_big_notes_5.png'
33 | }
34 |
35 | export default class Title {
36 | constructor(container){
37 |
38 | const titleContainer = document.createElement('div')
39 | titleContainer.id = "title"
40 | container.appendChild(titleContainer)
41 |
42 | const giorgi = document.createElement('div')
43 | giorgi.id = "giorgi"
44 | titleContainer.appendChild(giorgi)
45 |
46 | this._face = document.createElement('div')
47 | this._face.id='face'
48 | titleContainer.appendChild(this._face)
49 |
50 | this._faceImg = document.createElement('img')
51 |
52 | const cam = document.createElement('div')
53 | cam.id='cam'
54 | titleContainer.appendChild(cam)
55 |
56 | this._actionIndex = 0
57 |
58 | this._actions = ['_blink', '_sing', '_pause', '_blink', '_pause']
59 |
60 | // load all the images, then start the animation
61 | this._load().then(this._next.bind(this))
62 | }
63 |
64 | _load(){
65 | const promises = []
66 | Object.keys(images).forEach((key) => {
67 | promises.push(new Promise((success) => {
68 | let img = new Image()
69 | img.onload = success
70 | img.src = `images/face/${images[key]}`
71 | images[key] = img
72 | }))
73 | })
74 | return Promise.all(promises)
75 | }
76 |
77 | _setImage(img){
78 | this._faceImg.remove()
79 | this._face.appendChild(img)
80 | this._faceImg = img
81 | // this._face.style.backgroundImage = `url('../images/face/${img}')`
82 | }
83 |
84 | _blink(){
85 |
86 | this._setImage(images.rest)
87 |
88 | setTimeout(() => {
89 | this._setImage(images.blink)
90 | }, 150)
91 |
92 | setTimeout(() => {
93 | this._setImage(images.rest)
94 | }, 300)
95 |
96 | return new Promise((success) => {
97 | setTimeout(() => {
98 | success()
99 | }, 1000)
100 | })
101 | }
102 |
103 | _sing(){
104 |
105 | let wait = 0
106 | for (let i = 0; i <= 10; i++){
107 | setTimeout(() => {
108 | this._setImage(images[`sing${i}`])
109 | }, wait)
110 | wait += 150
111 | }
112 |
113 | setTimeout(() => {
114 | this._setImage(images.sing0)
115 | }, wait)
116 | wait += 300
117 |
118 | setTimeout(() => {
119 | this._setImage(images.rest)
120 | }, wait)
121 | wait += 800
122 |
123 | return new Promise((success) => {
124 | setTimeout(() => {
125 | success()
126 | }, wait)
127 | })
128 | }
129 |
130 | _pause(){
131 | return new Promise((success) => {
132 | setTimeout(() => {
133 | success()
134 | }, Math.random() * 1200 + 400)
135 | })
136 | }
137 |
138 | _next(){
139 | let action = this._actions[this._actionIndex]
140 | this._actionIndex = (this._actionIndex + 1) % this._actions.length
141 | this[action]().then(this._next.bind(this))
142 | }
143 | }
--------------------------------------------------------------------------------
/static/src/voice/Text.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import 'style/text.css'
18 | import Tone from 'Tone/core/Tone'
19 |
20 | const textContainer = document.createElement('div')
21 | textContainer.id = 'textContainer'
22 |
23 | const timeline = []
24 |
25 | function updateLoop(){
26 | requestAnimationFrame(updateLoop)
27 | let now = Tone.now()
28 | while (timeline.length && timeline[0].time <= now){
29 | timeline.shift().callback()
30 | }
31 | }
32 |
33 | function makeVisibleCallback(element){
34 | return () => {
35 | element.classList.add('visible')
36 | }
37 | }
38 |
39 | let shiftUp = false
40 |
41 | export default {
42 |
43 | appendTo : function(container){
44 | container.appendChild(textContainer)
45 | //start the loop
46 | updateLoop()
47 | },
48 |
49 | shiftUp : function(){
50 | shiftUp = true
51 | },
52 |
53 | shiftDown : function(){
54 | shiftUp = false
55 | },
56 |
57 | show : function(text, start, duration, labelObj){
58 | const words = text.split(/[ ,]+/)
59 | let wordTime = duration / words.length
60 |
61 | let utterance = document.createElement('div')
62 | utterance.classList.add('utterance')
63 | textContainer.appendChild(utterance)
64 |
65 | if (labelObj){
66 | const splitLabel = labelObj.label.split(/[ ,]+/)
67 | // get the index of the first word of the label
68 | let index = words.indexOf(splitLabel[0])
69 |
70 | labelObj.count = splitLabel.length
71 | labelObj.split = splitLabel
72 |
73 | // cut the words out and replace it with the label
74 | words.splice(index, splitLabel.length, labelObj.label)
75 | }
76 |
77 | if (shiftUp){
78 | utterance.classList.add('shiftUp')
79 | }
80 |
81 |
82 | timeline.push({
83 | time : start,
84 | callback : makeVisibleCallback(utterance)
85 | })
86 |
87 | let wordDelay = start
88 |
89 | for (let i = 0; i < words.length; i++){
90 | let word = words[i]
91 |
92 | let element = document.createElement('span')
93 | let wordCount = 1
94 |
95 | if (labelObj && word === labelObj.label){
96 | // element.textContent = labelObj.label
97 | element.classList.add('label')
98 |
99 | timeline.push({
100 | time : wordDelay,
101 | callback : makeVisibleCallback(element)
102 | })
103 |
104 | for (let lWord of labelObj.split){
105 | let subEl = document.createElement('span')
106 | subEl.textContent = lWord
107 | element.appendChild(subEl)
108 |
109 | timeline.push({
110 | time : wordDelay,
111 | callback : makeVisibleCallback(subEl)
112 | })
113 |
114 | wordDelay += wordTime
115 | }
116 |
117 | // add the score
118 | let scoreEl = document.createElement('div')
119 | scoreEl.classList.add('score')
120 | scoreEl.textContent = `${(labelObj.score * 100).toFixed()}% confidence`
121 | element.appendChild(scoreEl)
122 |
123 | timeline.push({
124 | time : wordDelay - wordTime,
125 | callback : makeVisibleCallback(scoreEl)
126 | })
127 |
128 | } else {
129 | element.textContent = words[i]
130 | timeline.push({
131 | time : wordDelay,
132 | callback : makeVisibleCallback(element)
133 | })
134 | wordDelay += wordTime
135 | }
136 | utterance.appendChild(element)
137 |
138 | }
139 |
140 | // remove it at the end
141 | timeline.push({
142 | time : start + duration + (labelObj ? 1.4 : 0.3),
143 | callback : () => {
144 | utterance.remove()
145 | }
146 | })
147 |
148 | updateLoop()
149 | },
150 |
151 | clear : function(){
152 | // textContainer.remove()
153 | //clear the text events
154 | textContainer.innerHTML = ''
155 | //flush all the events
156 | while(timeline.length){
157 | timeline.shift()
158 | }
159 | }
160 | }
--------------------------------------------------------------------------------
/static/third_party/Modernizr.js:
--------------------------------------------------------------------------------
1 | /*! modernizr 3.3.1 (Custom Build) | MIT *
2 | * https://modernizr.com/download/?-getusermedia-webaudio-webgl !*/
3 | !function(e,n,t){function r(e,n){return typeof e===n}function o(){var e,n,t,o,i,s,a;for(var u in g)if(g.hasOwnProperty(u)){if(e=[],n=g[u],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(t=0;td;d++)if(v=e[d],g=T.style[v],a(v,"-")&&(v=i(v)),T.style[v]!==t){if(u||r(o,"undefined"))return f(),"pfx"==n?v:!0;try{T.style[v]=o}catch(y){}if(T.style[v]!=g)return f(),"pfx"==n?v:!0}return f(),!1}function v(e,n,t,o,i){var s=e.charAt(0).toUpperCase()+e.slice(1),a=(e+" "+S.join(s+" ")+s).split(" ");return r(n,"string")||r(n,"undefined")?m(a,n,o,i):(a=(e+" "+b.join(s+" ")+s).split(" "),f(a,n,t))}var g=[],h={_version:"3.3.1",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var t=this;setTimeout(function(){n(t[e])},0)},addTest:function(e,n,t){g.push({name:e,fn:n,options:t})},addAsyncTest:function(e){g.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=h,Modernizr=new Modernizr,Modernizr.addTest("webaudio",function(){var n="webkitAudioContext"in e,t="AudioContext"in e;return Modernizr._config.usePrefixes?n||t:t});var y=[],C=n.documentElement,x="svg"===C.nodeName.toLowerCase();Modernizr.addTest("webgl",function(){var n=s("canvas"),t="probablySupportsContext"in n?"probablySupportsContext":"supportsContext";return t in n?n[t]("webgl")||n[t]("experimental-webgl"):"WebGLRenderingContext"in e});var w="Moz O ms Webkit",S=h._config.usePrefixes?w.split(" "):[];h._cssomPrefixes=S;var _=function(n){var r,o=prefixes.length,i=e.CSSRule;if("undefined"==typeof i)return t;if(!n)return!1;if(n=n.replace(/^@/,""),r=n.replace(/-/g,"_").toUpperCase()+"_RULE",r in i)return"@"+n;for(var s=0;o>s;s++){var a=prefixes[s],u=a.toUpperCase()+"_"+r;if(u in i)return"@-"+a.toLowerCase()+"-"+n}return!1};h.atRule=_;var b=h._config.usePrefixes?w.toLowerCase().split(" "):[];h._domPrefixes=b;var E={elem:s("modernizr")};Modernizr._q.push(function(){delete E.elem});var T={style:E.elem.style};Modernizr._q.unshift(function(){delete T.style}),h.testAllProps=v;var z=h.prefixed=function(e,n,t){return 0===e.indexOf("@")?_(e):(-1!=e.indexOf("-")&&(e=i(e)),n?v(e,n,t):v(e,"pfx"))};Modernizr.addTest("getusermedia",!!z("getUserMedia",navigator)),o(),delete h.addTest,delete h.addAsyncTest;for(var P=0;P,
4 | licensed under the SIL OFL 1.1 . (C) 2009-2016.
5 |
6 | -----------------------------------------------------------
7 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
8 | -----------------------------------------------------------
9 |
10 | PREAMBLE
11 | The goals of the Open Font License (OFL) are to stimulate worldwide
12 | development of collaborative font projects, to support the font creation
13 | efforts of academic and linguistic communities, and to provide a free and
14 | open framework in which fonts may be shared and improved in partnership
15 | with others.
16 |
17 | The OFL allows the licensed fonts to be used, studied, modified and
18 | redistributed freely as long as they are not sold by themselves. The
19 | fonts, including any derivative works, can be bundled, embedded,
20 | redistributed and/or sold with any software provided that any reserved
21 | names are not used by derivative works. The fonts and derivatives,
22 | however, cannot be released under any other type of license. The
23 | requirement for fonts to remain under this license does not apply
24 | to any document created using the fonts or their derivatives.
25 |
26 | DEFINITIONS
27 | "Font Software" refers to the set of files released by the Copyright
28 | Holder(s) under this license and clearly marked as such. This may
29 | include source files, build scripts and documentation.
30 |
31 | "Reserved Font Name" refers to any names specified as such after the
32 | copyright statement(s).
33 |
34 | "Original Version" refers to the collection of Font Software components as
35 | distributed by the Copyright Holder(s).
36 |
37 | "Modified Version" refers to any derivative made by adding to, deleting,
38 | or substituting -- in part or in whole -- any of the components of the
39 | Original Version, by changing formats or by porting the Font Software to a
40 | new environment.
41 |
42 | "Author" refers to any designer, engineer, programmer, technical
43 | writer or other person who contributed to the Font Software.
44 |
45 | PERMISSION & CONDITIONS
46 | Permission is hereby granted, free of charge, to any person obtaining
47 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
48 | redistribute, and sell modified and unmodified copies of the Font
49 | Software, subject to the following conditions:
50 |
51 | 1) Neither the Font Software nor any of its individual components,
52 | in Original or Modified Versions, may be sold by itself.
53 |
54 | 2) Original or Modified Versions of the Font Software may be bundled,
55 | redistributed and/or sold with any software, provided that each copy
56 | contains the above copyright notice and this license. These can be
57 | included either as stand-alone text files, human-readable headers or
58 | in the appropriate machine-readable metadata fields within text or
59 | binary files as long as those fields can be easily viewed by the user.
60 |
61 | 3) No Modified Version of the Font Software may use the Reserved Font
62 | Name(s) unless explicit written permission is granted by the corresponding
63 | Copyright Holder. This restriction only applies to the primary font name as
64 | presented to the users.
65 |
66 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
67 | Software shall not be used to promote, endorse or advertise any
68 | Modified Version, except to acknowledge the contribution(s) of the
69 | Copyright Holder(s) and the Author(s) or with their explicit written
70 | permission.
71 |
72 | 5) The Font Software, modified or unmodified, in part or in whole,
73 | must be distributed entirely under this license, and must not be
74 | distributed under any other license. The requirement for fonts to
75 | remain under this license does not apply to any document created
76 | using the Font Software.
77 |
78 | TERMINATION
79 | This license becomes null and void if any of the above conditions are
80 | not met.
81 |
82 | DISCLAIMER
83 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
84 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
85 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
86 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
87 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
88 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
89 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
90 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
91 | OTHER DEALINGS IN THE FONT SOFTWARE.
92 |
--------------------------------------------------------------------------------
/static/src/camera/Image.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Time from 'Tone/type/Time'
18 | import knuthShuffle from 'knuth-shuffle'
19 | const shuffle = knuthShuffle.knuthShuffle
20 |
21 | export default class Img {
22 | constructor(container){
23 |
24 | this._canvas = document.createElement('canvas')
25 | this._canvas.id = 'image'
26 | container.appendChild(this._canvas)
27 |
28 | this._context = this._canvas.getContext('2d')
29 |
30 | this._updateInterval = Time('4n').toMilliseconds();
31 |
32 | this._lastUpdate = Date.now()
33 |
34 | this._currentImg = null
35 |
36 | this._isVisible = false
37 |
38 | this._colorIndex = 0
39 | this._colors = [[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 0, 255], [255, 255, 0], [0, 255, 255]]
40 | // these colors are from JR:
41 | // this._colors = [[7,222,0],[9,51,255],[93,17,255],[101,177,255],[255,42,2],[255,181,115],[250,196,0],[5,153,0],[255,251,83],[46,220,0],[246,168,0],[182,87,255],[194,208,0]]
42 |
43 | this.resize()
44 | this._loop()
45 | }
46 |
47 | _loop(){
48 | requestAnimationFrame(this._loop.bind(this))
49 | if (this._isVisible && this._currentImg && Date.now() - this._lastUpdate > this._updateInterval){
50 | this._lastUpdate = Date.now()
51 | this._drawImg(this._currentImg)
52 | }
53 | }
54 |
55 | bounce(){
56 | this._canvas.classList.add('bounce')
57 | }
58 |
59 | show(){
60 | this._lastUpdate = 0
61 | this._canvas.classList.add('visible')
62 | this._isVisible = true
63 | }
64 |
65 | hide(){
66 | this._isVisible = false
67 | this._canvas.classList.remove('visible')
68 | this._canvas.classList.remove('bounce')
69 | this._currentImg = null
70 | }
71 |
72 | setImage(img){
73 | this._currentImg = img
74 | }
75 |
76 | resize(){
77 | this._context.canvas.width = window.innerWidth
78 | this._context.canvas.height = window.innerHeight
79 | }
80 |
81 | _grayScale(context){
82 |
83 | let color = this._colors[this._colorIndex]
84 | this._colorIndex = (this._colorIndex + 1) % this._colors.length
85 |
86 | let width = context.canvas.width
87 | let height = context.canvas.height
88 |
89 | let pixels = context.getImageData(0, 0, width, height);
90 |
91 | for(let y = 0; y < pixels.height; y++){
92 | for(let x = 0; x < pixels.width; x++){
93 | let i = (y * 4) * pixels.width + x * 4;
94 | let avg = (pixels.data[i] + pixels.data[i + 1] + pixels.data[i + 2]) / 3;
95 | pixels.data[i] = avg - (255 - color[0]);
96 | pixels.data[i + 1] = avg - (255 - color[1]);
97 | pixels.data[i + 2] = avg - (255 - color[2]);
98 | }
99 | }
100 |
101 | context.putImageData(pixels, 0, 0, 0, 0, pixels.width, pixels.height);
102 | }
103 |
104 | _drawImg(image){
105 | let context = this._context
106 | let imageW = image.width
107 | let imageH = image.height
108 | let canvasW = context.canvas.width
109 | let canvasH = context.canvas.height
110 | let canvasAspectRatio = canvasW / canvasH
111 | let imageAspectRatio = imageW / imageH
112 | // get the smaller of the frame
113 | if (canvasAspectRatio > imageAspectRatio){
114 | //fit width & scale height
115 | let widthScale = imageW / canvasW
116 | let scaleHeight = imageH / widthScale
117 | // the diff between the canvas width and the image width
118 | let topOffset = (scaleHeight - canvasH) / 2
119 | context.drawImage(image, 0, -topOffset, canvasW, scaleHeight)
120 | } else {
121 | //fit height & scale width
122 | let heightScale = imageH / canvasH
123 | let scaledWidth = imageW / heightScale
124 | // the diff between the canvas width and the image width
125 | let leftOffset = (scaledWidth - canvasW) / 2
126 | context.drawImage(image, -leftOffset, 0, scaledWidth, canvasH)
127 | }
128 | this._grayScale(context)
129 | }
130 |
131 | draw(context){
132 | if (this._currentImg && Date.now() - this._lastUpdate > this._updateInterval){
133 | this._lastUpdate = Date.now()
134 | this._drawImg(this._currentImg, context)
135 | }
136 | }
137 | }
--------------------------------------------------------------------------------
/static/images/badgeAI_master.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
59 |
--------------------------------------------------------------------------------
/static/src/camera/Canvas.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const THREE = require('three')
18 | import Time from 'Tone/type/Time'
19 | import TWEEN from 'tween.js'
20 |
21 | const CAMERA_DIST = 10
22 |
23 | const TWO_PI = Math.PI * 2
24 |
25 | export default class Canvas {
26 | constructor(container){
27 |
28 | this._camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 )
29 | this._camera.position.z = CAMERA_DIST
30 |
31 |
32 | this._scene = new THREE.Scene()
33 |
34 | this._renderer = new THREE.WebGLRenderer({ antialias: false })
35 | container.appendChild(this._renderer.domElement)
36 | this._renderer.domElement.id = 'three'
37 |
38 | // make a test plane
39 | const material = new THREE.MeshBasicMaterial({ color : 0xffffff })
40 | // material.side = THREE.DoubleSide
41 | this._plane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material)
42 |
43 | window.plane = this._plane
44 |
45 | this._scene.add(this._plane)
46 |
47 | this._bouncing = false
48 |
49 | this._bounceStart = 0
50 |
51 | this._bounceRate = Time('4n').toMilliseconds()
52 |
53 | this._changingColor = false
54 |
55 | this._colorChangeTime = 0
56 |
57 | this._colorChangeIndex = 0
58 |
59 | this._colors = [[7,222,0],[9,51,255],[93,17,255],[101,177,255],[255,42,2],[255,181,115],[250,196,0],[5,153,0],[255,251,83],[46,220,0],[246,168,0],[182,87,255],[194,208,0]]
60 | .map((rgb) => new THREE.Color(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255))
61 |
62 | this._loop()
63 | }
64 |
65 | _loop(time){
66 | requestAnimationFrame(this._loop.bind(this))
67 |
68 | let now = Date.now()
69 |
70 | if (this._bouncing){
71 | // let bounceAmount = Math.sin(this._bounceRate * TWO_PI * (now - this._bounceStart) / 1000)
72 | // bounceAmount = (bounceAmount + 1)
73 | let bounceAmount = (now - this._bounceStart) % this._bounceRate
74 | bounceAmount /= this._bounceRate
75 | this._camera.position.z = CAMERA_DIST - (1 - bounceAmount) / 2
76 | }
77 |
78 | if (this._changingColor && ((now - this._colorChangeTime) > (this._bounceRate))){
79 | this._plane.material.color = this._colors[this._colorChangeIndex]
80 | this._colorChangeIndex = (this._colorChangeIndex + 1) % this._colors.length
81 | this._colorChangeTime = now
82 | }
83 |
84 | this._renderer.render( this._scene, this._camera )
85 |
86 | TWEEN.update(time);
87 | }
88 |
89 | changeColor(){
90 | this._changingColor = true
91 | this._colorChangeTime = Date.now()
92 | }
93 |
94 | repeatTexture(){
95 | const texture = this._plane.material.map
96 | window.texture = texture
97 | texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;
98 | const repeats = [1, 2, 4, 8]
99 | let index = 0
100 | this._interval = setInterval(() => {
101 | texture.repeat.x = repeats[index]
102 | texture.repeat.y = repeats[index]
103 | index = (index + 1) % repeats.length
104 | }, this._bounceRate)
105 | }
106 |
107 | endChangeColor(){
108 | this._changingColor = false
109 | this._plane.material.color = new THREE.Color(1, 1, 1)
110 | // clearInterval(this._interval)
111 | // this._plane.material.map.repeat.x = 1
112 | // this._plane.material.map.repeat.y = 1
113 | }
114 |
115 | /**
116 | * mirror the display for front facing cameras
117 | */
118 | mirror(){
119 | this._plane.scale.x *= -1
120 | this._plane.material.side = THREE.BackSide
121 | this._plane.material.needsUpdate = true
122 | }
123 |
124 | resize(vidWidth, vidHeight){
125 |
126 |
127 |
128 | //set the video plane size
129 | const vidAspect = vidWidth / vidHeight
130 | const camAspect = window.innerWidth / window.innerHeight
131 |
132 | this._plane.scale.x = vidAspect
133 |
134 | //http://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
135 | if (vidAspect > camAspect){
136 | this._camera.fov = 2 * Math.atan( 1 / ( 2 * CAMERA_DIST ) ) * ( 180 / Math.PI )
137 | } else {
138 | this._camera.fov = 2 * Math.atan( ( vidAspect / camAspect ) / ( 2 * CAMERA_DIST ) ) * ( 180 / Math.PI )
139 | }
140 |
141 | //THREE resize
142 | this._renderer.setPixelRatio( window.devicePixelRatio )
143 | this._renderer.setSize( window.innerWidth, window.innerHeight )
144 |
145 | const windowHalfX = window.innerWidth / 2
146 | const windowHalfY = window.innerHeight / 2
147 | this._camera.aspect = camAspect
148 | this._camera.updateProjectionMatrix()
149 | this._renderer.setSize( window.innerWidth, window.innerHeight )
150 | }
151 |
152 | setVideo(video){
153 |
154 | this._renderer.domElement.classList.add('visible')
155 |
156 | const texture = new THREE.VideoTexture(video)
157 | texture.minFilter = THREE.LinearFilter;
158 | texture.magFilter = THREE.LinearFilter;
159 | this._plane.material.map = texture
160 | this._plane.material.needsUpdate = true
161 | }
162 |
163 | bounce(){
164 | this._bouncing = true
165 | this._bounceStart = Date.now()
166 | }
167 |
168 | endBounce(){
169 | this._bouncing = false
170 | }
171 |
172 | fail(time = 4000){
173 | this._bouncing = false
174 | return new Promise((end) => {
175 | let plane = this._plane
176 | let scene = this._scene
177 | var tween = new TWEEN.Tween({scale : 1})
178 | .to({scale : 0}, time)
179 | .onUpdate(function(){
180 | plane.scale.y = this.scale
181 | })
182 | .onComplete(function(){
183 | scene.remove(plane)
184 | end()
185 | })
186 | .easing(TWEEN.Easing.Quadratic.Out)
187 | .start()
188 | })
189 | }
190 | }
--------------------------------------------------------------------------------
/static/style/splash.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | @import 'common.css';
18 |
19 | $topMargin: 40px;
20 |
21 | #splash {
22 | font-weight: bold;
23 | position: absolute;
24 | width: 100%;
25 | height: 100%;
26 | top: 0px;
27 | left: 0px;
28 | background-color: black;
29 | color: white;
30 |
31 | transition: opacity 0.1s;
32 |
33 | &.disappear {
34 | opacity: 0;
35 | pointer-events: none;
36 | }
37 |
38 | * {
39 | line-height: 30px;
40 | // font-size: 16px;
41 | font-size: 20px;
42 | width: 80%;
43 | margin-left: auto;
44 | margin-right: auto;
45 | text-align: center;
46 | }
47 |
48 | #learnMore {
49 | margin-top: $topMargin;
50 | font-size: 18px;
51 | display: block;
52 | width: 100px;
53 | @mixin yellowLink;
54 | }
55 |
56 | #titleContainer {
57 | position: absolute;
58 | top: 50%;
59 | left: 50%;
60 | width: 80%;
61 | transform: translate(-50%, -50%);
62 | text-align: center;
63 | min-width: 300px;
64 |
65 | $pink : rgb(255, 82, 184);
66 | $purple : rgb(115, 58, 236);
67 | $yellow : rgb(255, 241, 6);
68 | $teal : rgb(10, 247, 247);
69 |
70 | #title {
71 | line-height: 60px;
72 | font-size: 50px;
73 | letter-spacing: 1px;
74 | text-transform: uppercase;
75 |
76 | span {
77 | line-height: 60px;
78 | font-size: 50px;
79 | margin-right: 1px;
80 |
81 | &.pink {
82 | color: $pink;
83 | }
84 |
85 | &.purple {
86 | color: $purple;
87 | }
88 |
89 | &.teal {
90 | color: $teal;
91 | }
92 |
93 | &.yellow {
94 | color: $yellow;
95 | }
96 | }
97 | }
98 |
99 | #subTitle {
100 | margin-top: $topMargin;
101 | letter-spacing: 0.8px;
102 | line-height: 30px;
103 | font-size: 22px;
104 | width: 80%;
105 | margin-left: auto;
106 | margin-right: auto;
107 | text-align: center;
108 | text-transform: uppercase;
109 | }
110 |
111 | $loaderWidth: 160px;
112 | $loaderHeight: 55px;
113 |
114 | #loader {
115 | position: relative;
116 | margin-top: $topMargin;
117 | background-color: black;
118 | border: 3px solid white;
119 | width: $loaderWidth;
120 | height: $loaderHeight;
121 | margin-left: auto;
122 | margin-right: auto;
123 | text-transform: uppercase;
124 | overflow: hidden;
125 |
126 | &.clickable {
127 | cursor: pointer;
128 |
129 | transition: transform 0.1s;
130 |
131 | &:hover {
132 | transform: scale(1.1);
133 | }
134 |
135 | #fillText:active {
136 | color: black!important;
137 | background-color: white;
138 | }
139 | }
140 |
141 | #loaderText {
142 | position: absolute;
143 | width: 100%;
144 | height: 100%;
145 | color: black;
146 | background-color: white;
147 | }
148 |
149 | #fill {
150 | position: absolute;
151 | height: 100%;
152 | width: 0%;
153 | overflow: hidden;
154 | background-color: black;
155 |
156 | #fillText {
157 | width: $loaderWidth;
158 | height: 100%;
159 | color: white;
160 |
161 | &:before{
162 | background-color: white;
163 | }
164 | }
165 | }
166 |
167 | #loaderText, #fillText {
168 | line-height: $loaderHeight;
169 | font-size: 22px;
170 | text-align: center;
171 | font-weight: normal;
172 | padding-left: 18px;
173 |
174 | &:before {
175 | content : ' ';
176 | mask-image : url(../images/camera_icon.svg);
177 | position: absolute;
178 | left: 22px;
179 | top: 17px;
180 | width: 25px;
181 | height: 25px;
182 | background-repeat: no-repeat;
183 | mask-repeat: no-repeat;
184 | background-color: black;
185 | }
186 | }
187 | }
188 |
189 | }
190 |
191 | #buildWith {
192 | margin-top: $topMargin;
193 | }
194 |
195 | $badgeWidth : 80px;
196 | $badgeHeight: 50px;
197 | $badgeMargin : 20px;
198 | $badegOpacity: 0.7;
199 |
200 | #aiExperiments, #googleFriends {
201 | width: $badgeWidth;
202 | height: $badgeHeight;
203 | position: absolute;
204 | bottom: $badgeMargin;
205 | opacity: $badegOpacity;
206 | background-repeat: no-repeat;
207 | background-size: 100% 100%;
208 | }
209 |
210 |
211 | #aiExperiments {
212 | left: $badgeMargin;
213 | background-image: url(../images/badgeAI_master.svg);
214 |
215 | &:hover {
216 | opacity: 1;
217 |
218 | &:active {
219 | opacity: 0.3;
220 | }
221 | }
222 | }
223 |
224 | #googleFriends {
225 | cursor: initial;
226 | left: calc($badgeWidth + $badgeMargin * 3);
227 | background-image: url(../images/badgeFriends_master.svg);
228 | }
229 |
230 | #badgeBreak{
231 | $breakHeight: calc($badgeHeight * 0.8);
232 | height: $breakHeight;
233 | left: calc($badgeWidth + $badgeMargin * 1.8);
234 | background-color: white;
235 | opacity: calc($badegOpacity / 2);
236 | position: absolute;
237 | width: 1px;
238 | bottom: calc($badgeMargin + ($badgeHeight - $breakHeight) / 2);
239 | }
240 |
241 | #privacyAndTerms {
242 | position: absolute;
243 | bottom: $badgeMargin;
244 | right: $badgeMargin;
245 | width: auto;
246 |
247 | * {
248 | height: 14px;
249 | line-height: 14px;
250 | font-size: 14px;
251 | color: white;
252 | display: inline;
253 | opacity: $badegOpacity;
254 | margin: 2px;
255 | }
256 |
257 | a {
258 | text-decoration: none;
259 | cursor: pointer;
260 |
261 | &:hover {
262 | opacity: 1;
263 |
264 | &:active {
265 | opacity: 0.3;
266 | }
267 | }
268 | }
269 | }
270 | }
--------------------------------------------------------------------------------
/static/images/badgeFriends_master.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
84 |
--------------------------------------------------------------------------------