Compare commits

..

9 Commits

Author SHA1 Message Date
harmse
31098054e6 fix typos and formatting on splash page 2018-10-26 11:24:35 -04:00
harmse
5391f3f028 update midi keyboard to fix octave issue 2018-10-24 10:43:49 -04:00
harmse
cb63c392ae removing about page, privacy and terms, and external links 2018-10-23 12:02:06 -04:00
harmse
cb21cce287 add about page back, but without video and with more text 2018-09-28 16:04:17 -04:00
harmse
49324d2dc5 update style of splash page 2018-09-28 13:35:52 -04:00
harmse
25c26cda56 switch german/english on splash page 2018-09-28 13:33:56 -04:00
harmse
0afc8f69f6 update so that keyboard and midi both trigger 2018-08-08 14:06:27 -04:00
harmse
896d778dee update loader 2018-08-08 13:16:29 -04:00
harmse
0033ff4a5c updates for heinz-nixdorf installation 2018-08-06 17:43:00 -04:00
15 changed files with 335 additions and 196 deletions

View File

@ -1,6 +1,4 @@
FROM ubuntu:18.04 FROM ubuntu:14.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
pkg-config \ pkg-config \
@ -10,28 +8,19 @@ RUN apt-get update && apt-get install -y \
libblas-dev \ libblas-dev \
liblapack-dev \ liblapack-dev \
libatlas-base-dev \ libatlas-base-dev \
libsndfile1-dev \
libasound2-dev \
libjack-dev \
gfortran \ gfortran \
ffmpeg \
llvm-8 \
python \ python \
python2.7 \ python-dev \
python3 \ python-pip \
python3-dev \ curl && \
python3-pip \ curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash - && \
python3-venv \ apt-get install -y nodejs
nvidia-cuda-dev \
curl \
nodejs \
npm && \
apt clean
RUN pip3 install --upgrade pip
RUN pip install -U https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.12.1-cp27-none-linux_x86_64.whl
COPY ./server/requirements.txt /tmp/ COPY ./server/requirements.txt /tmp/
RUN pip3 install -r /tmp/requirements.txt RUN pip install -r /tmp/requirements.txt
COPY . /src/ COPY . /src/
@ -41,4 +30,4 @@ RUN npm install && npm run build
WORKDIR /src/server/ WORKDIR /src/server/
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT python3 server.py ENTRYPOINT python server.py

View File

@ -18,6 +18,35 @@ Built by [Yotam Mann](https://github.com/tambien) with friends on the Magenta an
A.I. Duet is composed of two parts, the front-end which is in the `static` folder and the back-end which is in the `server` folder. The front-end client creates short MIDI files using the players's input which is sent to a [Flask](http://flask.pocoo.org/) server. The server takes that MIDI input and "continues" it using [Magenta](https://github.com/tensorflow/magenta) and [TensorFlow](https://www.tensorflow.org/) which is then returned back to the client. A.I. Duet is composed of two parts, the front-end which is in the `static` folder and the back-end which is in the `server` folder. The front-end client creates short MIDI files using the players's input which is sent to a [Flask](http://flask.pocoo.org/) server. The server takes that MIDI input and "continues" it using [Magenta](https://github.com/tensorflow/magenta) and [TensorFlow](https://www.tensorflow.org/) which is then returned back to the client.
## INSTALLATION
A.I. Duet only works with [Python 2.7](https://www.python.org/download/releases/2.7/) and it was tested with Node v6. There are two basic ways of installing A.I. Duet: with Docker or without Docker.
If you already have a Python environment setup, install all of the server dependencies and start the server by typing the following in the terminal:
```bash
cd server
pip install -r requirements.txt
```
If this does not work, jump down to the [Docker](#docker) installation instructions, which will walk you through installing A.I. Duet within a Docker container.
If it _did_ install tensorflow and magenta successfully, you can run the server by typing:
```bash
python server.py
```
Then to build and install the front-end Javascript code, first make sure you have [Node.js](https://nodejs.org) 6 installed. And then install of the dependencies of the project and build the code by typing the following in the terminal:
```bash
cd static
npm install
npm run build
```
You can now play with A.I. Duet at [localhost:8080](http://localhost:8080).
## DOCKER ## DOCKER
[Docker](https://www.docker.com/) is an open-source containerization software which simplifies installation across various OSes. It is the simplest method to build and install both the front-end and back-end components. Once you have Docker installed, you can just run: [Docker](https://www.docker.com/) is an open-source containerization software which simplifies installation across various OSes. It is the simplest method to build and install both the front-end and back-end components. Once you have Docker installed, you can just run:
@ -29,15 +58,6 @@ $ sudo docker run -t -p 8080:8080 ai-duet
You can now play with A.I. Duet at [localhost:8080](http://localhost:8080). You can now play with A.I. Duet at [localhost:8080](http://localhost:8080).
## DOCKER-COMPOSE
```bash
$ docker-compose build
$ docker-compose up -d
```
You can now play with A.I. Duet at [localhost:8080](http://localhost:8080).
## MIDI SUPPORT ## MIDI SUPPORT
The A.I. Duet supports MIDI keyboard input using [Web Midi API](https://webaudio.github.io/web-midi-api/) and the [WebMIDI](https://github.com/cotejp/webmidi) library. The A.I. Duet supports MIDI keyboard input using [Web Midi API](https://webaudio.github.io/web-midi-api/) and the [WebMIDI](https://github.com/cotejp/webmidi) library.

View File

@ -1,19 +0,0 @@
version: '2'
services:
aiduet:
build: ./
image: aiduet
container_name: aiduet
hostname: aiduet
restart: "no"
environment:
- NVIDIA_VISIBLE_DEVICES=all
devices:
- /dev/nvidia0
- /dev/nvidiactl
- /dev/nvidia-uvm
- /dev/nvidia-uvm-tools
cap_add:
- IPC_LOCK
network_mode: host

View File

@ -1,6 +1,5 @@
tensorflow-gpu==1.14.0 tensorflow==0.12.1
magenta==1.1.8 magenta==0.1.8
numba==0.53.1 Flask==0.12
Flask gunicorn==19.6.0
gunicorn ipython==5.1.0
ipython

View File

@ -22,7 +22,7 @@ import sys
if sys.version_info.major <= 2: if sys.version_info.major <= 2:
from cStringIO import StringIO from cStringIO import StringIO
else: else:
from io import StringIO, BytesIO from io import StringIO
import time import time
import json import json
@ -34,8 +34,7 @@ app = Flask(__name__, static_url_path='', static_folder=os.path.abspath('../stat
def predict(): def predict():
now = time.time() now = time.time()
values = json.loads(request.data) values = json.loads(request.data)
# midi_data = pretty_midi.PrettyMIDI(StringIO(''.join(chr(v) for v in values))) midi_data = pretty_midi.PrettyMIDI(StringIO(''.join(chr(v) for v in values)))
midi_data = pretty_midi.PrettyMIDI(BytesIO(b''.join((v).to_bytes(1, 'little') for v in values)))
duration = float(request.args.get('duration')) duration = float(request.args.get('duration'))
ret_midi = generate_midi(midi_data, duration) ret_midi = generate_midi(midi_data, duration)
return send_file(ret_midi, attachment_filename='return.mid', return send_file(ret_midi, attachment_filename='return.mid',

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 292.7 119.8" style="enable-background:new 0 0 292.7 119.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FBBC05;}
</style>
<g>
<polyline class="st0" points="131,92 131,65.9 124.7,65.9 124.7,30.4 110.2,30.4 110.2,92 "/>
<polyline class="st0" points="155.9,92 155.9,65.9 149.7,65.9 149.7,30.4 141.4,30.4 141.4,65.9 135.1,65.9 135.1,92 "/>
<polyline class="st0" points="255.8,92 255.8,65.9 249.6,65.9 249.6,30.4 241.3,30.4 241.3,65.9 235,65.9 235,92 "/>
<polyline class="st0" points="180.9,92 180.9,65.9 180.9,30.4 174.6,30.4 166.3,30.4 166.3,65.9 160.1,65.9 160.1,92 "/>
<polyline class="st0" points="205.9,92 205.9,65.9 199.6,65.9 199.6,30.4 185.1,30.4 185.1,92 "/>
<polyline class="st0" points="230.8,92 230.8,65.9 224.6,65.9 224.6,30.4 216.3,30.4 216.3,65.9 210,65.9 210,92 "/>
<polyline class="st0" points="280.8,92 280.8,65.9 280.8,30.4 274.5,30.4 266.2,30.4 266.2,65.9 260,65.9 260,92 "/>
<polyline class="st0" points="81.1,92 81.1,65.9 74.9,65.9 74.9,30.4 66.6,30.4 66.6,65.9 60.3,65.9 60.3,92 "/>
<polyline class="st0" points="31.2,92 31.2,65.9 24.9,65.9 24.9,30.4 10.4,30.4 10.4,92 "/>
<polyline class="st0" points="56.1,92 56.1,65.9 49.9,65.9 49.9,30.4 41.6,30.4 41.6,65.9 35.3,65.9 35.3,92 "/>
<polyline class="st0" points="106.1,92 106.1,65.9 106.1,30.4 99.8,30.4 91.5,30.4 91.5,65.9 85.3,65.9 85.3,92 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -23,6 +23,7 @@
"events": "^1.1.0", "events": "^1.1.0",
"exports-loader": "^0.6.3", "exports-loader": "^0.6.3",
"file-loader": "^0.9.0", "file-loader": "^0.9.0",
"idle-timeout": "^1.0.0",
"jsmidgen": "^0.1.5", "jsmidgen": "^0.1.5",
"midi-file-parser": "^1.0.0", "midi-file-parser": "^1.0.0",
"midiconvert": "0.4.1", "midiconvert": "0.4.1",

View File

@ -22,33 +22,49 @@ import {Splash} from 'interface/Splash'
import {About} from 'interface/About' import {About} from 'interface/About'
import {Tutorial} from 'ai/Tutorial' import {Tutorial} from 'ai/Tutorial'
import 'babel-polyfill' import 'babel-polyfill'
import IdleTimeout from 'idle-timeout'
/////////////// SPLASH /////////////////// /////////////// SPLASH ///////////////////
// const about = new About(document.body)
const about = new About(document.body)
const splash = new Splash(document.body) const splash = new Splash(document.body)
splash.on('click', () => { splash.on('click', () => {
keyboard.activate() keyboard.activate()
tutorial.start() tutorial.start()
about.showButton() // about.showButton()
})
splash.on('about', () => {
about.open(true)
})
about.on('close', () => {
if (!splash.loaded || splash.isOpen()){
splash.show()
} else {
keyboard.activate()
}
})
about.on('open', () => {
keyboard.deactivate()
if (splash.isOpen()){
splash.hide()
}
}) })
// splash.on('about', () => {
// about.open(true)
// })
// about.on('close', () => {
// if (!splash.loaded || splash.isOpen()){
// splash.show()
// keyboard._active = true
// } else {
//
// keyboard.activate()
// }
// })
// about.on('open', () => {
// keyboard.deactivate()
// if (splash.isOpen()){
// splash.hide()
// }
// })
//////////////// IDLE TIMER ///////////////
const idleTimeout = new IdleTimeout(
() => {
keyboard.deactivate();
// about.close();
splash.show();
keyboard._active = true;
},
{
element: document,
timeout: 60000,
loop: false
}
);
/////////////// PIANO /////////////////// /////////////// PIANO ///////////////////
@ -63,6 +79,7 @@ const sound = new Sound()
sound.load() sound.load()
keyboard.on('keyDown', (note) => { keyboard.on('keyDown', (note) => {
idleTimeout.reset()
sound.keyDown(note) sound.keyDown(note)
ai.keyDown(note) ai.keyDown(note)
glow.user() glow.user()

View File

@ -23,12 +23,28 @@ const tfLink = 'https://www.tensorflow.org/'
const toneLink = 'https://github.com/Tonejs/Tone.js' const toneLink = 'https://github.com/Tonejs/Tone.js'
const sourceCode = 'https://github.com/googlecreativelab/aiexperiments-ai-duet' const sourceCode = 'https://github.com/googlecreativelab/aiexperiments-ai-duet'
const blurbCopy = `Built by Yotam Mann with friends on the Magenta and Creative Lab teams at Google. const germanAboutCopy = `AI Duet nutzt maschinelles Lernen, um zu entscheiden,
It uses <a target='_blank' href='${tfLink}'>TensorFlow</a>, wie es auf die Noten reagiert, die du spielst. Dafür wurde
<a target='_blank' href='${toneLink}'>Tone.js</a> and tools das neuronale Netz mit einer Vielzahl an Melodien trainiert.
from the <a target='_blank' href='${magentaLink}'>Magenta project</a>. das neuronale Netz mit einer Vielzahl an Melodien trainiert.
The open-source code is <a target='_blank' href='${sourceCode}'>available here</a>. Noten and Takten. Regeln dafür, wie Noten oder Akkorde
Click the keyboard, use your computer keys, or even plug in a MIDI keyboard.` verarbeitet werden sollen, wurden dem System nicht gegeben.`
const englishAboutCopy = `AI Duet uses machine learning in order to decide on
how it responds to the notes you play. To do so,
a neural network was trained with lots of melodies.
Over time, the system learned about relationships
between notes and timing. No rules predefine how
notes or chords should be treated.`
const germanMadeByCopy = `Programmierung und Konzept: Yotam Mann und Googles
Creative Lab. Neuronales Netz: Googles Magenta project.
Der gesamte Quellcode ist Open Source: g.co/aiexperiments`
const englishMadeByCopy = `Programming and Concept: Yotam Mann and Googles
Creative Lab. Neural net: Googles Magenta project.
All the code is open source: g.co/aiexperiments`
export class About extends events.EventEmitter{ export class About extends events.EventEmitter{
constructor(container){ constructor(container){
@ -59,13 +75,13 @@ export class About extends events.EventEmitter{
const title = document.createElement('div') const title = document.createElement('div')
title.id = 'title' title.id = 'title'
title.textContent = 'A.I. Duet' title.textContent = 'A.I. Duet'
// content.appendChild(title) content.appendChild(title)
const video = document.createElement('div') const video = document.createElement('div')
video.id = 'video' video.id = 'video'
//vid YT0k99hCY5I //vid YT0k99hCY5I
video.innerHTML = `<iframe id='youtube-iframe' src="https://www.youtube.com/embed/0ZE1bfPtvZo?modestbranding=0&showinfo=0&enablejsapi=1" frameborder="0" allowfullscreen></iframe>` video.innerHTML = `<iframe id='youtube-iframe' src="https://www.youtube.com/embed/0ZE1bfPtvZo?modestbranding=0&showinfo=0&enablejsapi=1" frameborder="0" allowfullscreen></iframe>`
content.appendChild(video) // content.appendChild(video)
this._ytplayer = null this._ytplayer = null
@ -84,21 +100,38 @@ export class About extends events.EventEmitter{
}) })
}) })
const blurb = document.createElement('div') const germanAbout = document.createElement('div')
blurb.id = 'blurb' germanAbout.id = 'germanAbout'
content.appendChild(blurb) content.appendChild(germanAbout)
blurb.innerHTML = blurbCopy germanAbout.innerHTML = germanAboutCopy
const englishAbout = document.createElement('div')
englishAbout.id = 'englishAbout'
content.appendChild(englishAbout)
englishAbout.innerHTML = englishAboutCopy
const germanMadeBy = document.createElement('div')
germanMadeBy.id = 'germanMadeBy'
content.appendChild(germanMadeBy)
germanMadeBy.innerHTML = germanMadeByCopy
const englishMadeBy = document.createElement('div')
englishMadeBy.id = 'englishMadeBy'
content.appendChild(englishMadeBy)
englishMadeBy.innerHTML = englishMadeByCopy
} }
close(){ close(){
this._toggleButton.classList.remove('close') this._toggleButton.classList.remove('close')
this._toggleButton.classList.add('open') this._toggleButton.classList.add('open')
this._container.classList.remove('visible') this._container.classList.remove('visible')
if (this._ytplayer && this._ytplayer.stopVideo){ // if (this._ytplayer && this._ytplayer.stopVideo){
this._ytplayer.stopVideo() // this._ytplayer.stopVideo()
} // }
this.emit('close') this.emit('close')
if (window.ga){ if (window.ga){
ga('send', 'event', 'AI-Duet', 'Click', 'About - Close') ga('send', 'event', 'AI-Duet', 'Click', 'About - Close')
@ -108,25 +141,25 @@ export class About extends events.EventEmitter{
this._toggleButton.classList.add('close') this._toggleButton.classList.add('close')
this._toggleButton.classList.remove('open') this._toggleButton.classList.remove('open')
this._playButton.classList.add('visible') // this._playButton.classList.add('visible')
this._container.classList.add('visible') this._container.classList.add('visible')
this.emit('open') this.emit('open')
if (window.ga){ if (window.ga){
ga('send', 'event', 'AI-Duet', 'Click', 'About - Open') ga('send', 'event', 'AI-Duet', 'Click', 'About - Open')
} }
if (play){ // if (play){
this._playVideo() // this._playVideo()
} // }
} }
// waits until the player is ready to play the video, // waits until the player is ready to play the video,
// otherwise goes back into waiting loop // otherwise goes back into waiting loop
_playVideo(retries=0){ // _playVideo(retries=0){
if (this._ytplayer && this._ytplayer.playVideo){ // if (this._ytplayer && this._ytplayer.playVideo){
this._ytplayer.playVideo() // this._ytplayer.playVideo()
} else if (retries < 10 && this.isOpen()){ // } else if (retries < 10 && this.isOpen()){
setTimeout(() => this._playVideo(retries+1), 200); // setTimeout(() => this._playVideo(retries+1), 200);
} // }
} // }
isOpen(){ isOpen(){
return this._container.classList.contains('visible') return this._container.classList.contains('visible')
} }

View File

@ -47,16 +47,9 @@ export default class Loader extends EventEmitter{
this.loaded = false this.loaded = false
Buffer.on('load', () => { Buffer.on('load', () => {
this.loaded = true this.loaded = true
loader.classList.add("loaded")
fillText.innerHTML = '<div id="piano"></div> <div id="play">PLAY</div>' fillText.innerHTML = '<div id="piano">'
loader.classList.add('clickable')
loader.addEventListener('click', () => {
this.emit('click')
})
}) })
Buffer.on('progress', (prog) => { Buffer.on('progress', (prog) => {

View File

@ -38,9 +38,13 @@ class Splash extends events.EventEmitter{
titleContainer.appendChild(title) titleContainer.appendChild(title)
const subTitle = document.createElement('div') const subTitle = document.createElement('div')
const germanSubTitle = document.createElement('div')
subTitle.id = 'subTitle' subTitle.id = 'subTitle'
germanSubTitle.id = 'germanSubTitle'
titleContainer.appendChild(germanSubTitle)
titleContainer.appendChild(subTitle) titleContainer.appendChild(subTitle)
subTitle.textContent = 'A piano that responds to you.' subTitle.textContent = 'A piano that responds to you.'
germanSubTitle.textContent = ' Ein Klavier, das auf deine Eingaben antwortet.'
this._clicked = false this._clicked = false
const loader = this._loader = new Loader(titleContainer) const loader = this._loader = new Loader(titleContainer)
@ -51,12 +55,21 @@ class Splash extends events.EventEmitter{
}) })
const howItWorks = document.createElement('div') const howItWorks = document.createElement('div')
const germanHowItWorks = document.createElement('div')
howItWorks.id = 'howItWorks' howItWorks.id = 'howItWorks'
germanHowItWorks.id = 'germanHowItWorks'
titleContainer.appendChild(germanHowItWorks)
titleContainer.appendChild(howItWorks) titleContainer.appendChild(howItWorks)
howItWorks.textContent = 'How it works' howItWorks.innerHTML = 'This experiment lets you play a duet with a computer using machine learning.<br>Just play some notes and the computer will respond to your melody.<br>Over time, the system learns about the relationships<br>between notes and timing to play better duets.'
howItWorks.addEventListener('click', () => { germanHowItWorks.innerHTML = 'Mithilfe von maschinellem Lernen kannst du bei diesem Projekt<br>mit dem Computer im Duett spielen. Wenn du eine Melodie anstimmst,<br>wird der Algorithmus auf diese Noten reagieren. Im Laufe der Zeit erlernt<br>das System Bezüge zwischen Noten und Takten und verbessert seine Ergebnisse.<br><br>'
this.emit('about')
}) // const aboutPageLink = document.createElement('div')
// aboutPageLink.id = 'aboutPageLink'
// titleContainer.appendChild(aboutPageLink)
// aboutPageLink.textContent = 'How it works'
// aboutPageLink.addEventListener('click', () => {
// this.emit('about')
// })
const badges = document.createElement('div') const badges = document.createElement('div')
badges.id = 'badges' badges.id = 'badges'
@ -64,7 +77,7 @@ class Splash extends events.EventEmitter{
const aiExperiments = document.createElement('a') const aiExperiments = document.createElement('a')
aiExperiments.id = 'aiExperiments' aiExperiments.id = 'aiExperiments'
aiExperiments.href = 'https://aiexperiments.withgoogle.com' // aiExperiments.href = 'https://aiexperiments.withgoogle.com'
aiExperiments.target = '_blank' aiExperiments.target = '_blank'
aiExperiments.classList.add('badge') aiExperiments.classList.add('badge')
badges.appendChild(aiExperiments) badges.appendChild(aiExperiments)
@ -85,7 +98,7 @@ class Splash extends events.EventEmitter{
badges.appendChild(break1) badges.appendChild(break1)
const magenta = document.createElement('a') const magenta = document.createElement('a')
magenta.href = 'https://magenta.tensorflow.org/' // magenta.href = 'https://magenta.tensorflow.org/'
magenta.target = '_blank' magenta.target = '_blank'
magenta.id = 'magentaLink' magenta.id = 'magentaLink'
magenta.classList.add('badge') magenta.classList.add('badge')
@ -93,10 +106,11 @@ class Splash extends events.EventEmitter{
magenta.innerHTML = imgHtml + '<div id="text">Built using <span>Magenta</span></div>' magenta.innerHTML = imgHtml + '<div id="text">Built using <span>Magenta</span></div>'
badges.appendChild(magenta) badges.appendChild(magenta)
const privacyAndTerms = document.createElement('div') // const privacyAndTerms = document.createElement('div')
privacyAndTerms.id = 'privacyAndTerms' // privacyAndTerms.id = 'privacyAndTerms'
privacyAndTerms.innerHTML = '<a target="_blank" href="https://www.google.com/intl/en/policies/privacy/">Privacy</a><span>&</span><a target="_blank" href="https://www.google.com/intl/en/policies/terms/">Terms</a>' // // privacyAndTerms.innerHTML = '<a target="_blank" href="https://www.google.com/intl/en/policies/privacy/">Privacy</a><span>&</span><a target="_blank" href="https://www.google.com/intl/en/policies/terms/">Terms</a>'
splash.appendChild(privacyAndTerms) // privacyAndTerms.innerHTML = '<a target="_blank">Privacy</a><span>&</span><a target="_blank">Terms</a>'
// splash.appendChild(privacyAndTerms)
} }

View File

@ -28,7 +28,7 @@ class Keyboard extends events.EventEmitter{
this._container = container this._container = container
this._active = false this._active = true;
/** /**
* The audio key keyboard * The audio key keyboard
@ -76,9 +76,9 @@ class Keyboard extends events.EventEmitter{
//the midi input //the midi input
this._midi = new Midi() this._midi = new Midi()
this._midi.on('keyDown', (note) => { this._midi.on('keyDown', (note) => {
this.keyDown(note) this.keyDown(note);
this._emitKeyDown(note) this._emitKeyDown(note);
}) });
this._midi.on('keyUp', (note) => { this._midi.on('keyUp', (note) => {
this.keyUp(note) this.keyUp(note)
this._emitKeyUp(note) this._emitKeyUp(note)
@ -111,6 +111,9 @@ class Keyboard extends events.EventEmitter{
if (!this._active){ if (!this._active){
return return
} }
const splash = document.getElementById("splash");
splash.classList.add('disappear');
container.classList.add('focus');
if (!this._currentKeys[note]){ if (!this._currentKeys[note]){
this._currentKeys[note] = 0 this._currentKeys[note] = 0
} }

View File

@ -48,14 +48,14 @@ class Midi extends events.EventEmitter{
}) })
inputDevice.addListener('noteon', 'all', (event) => { inputDevice.addListener('noteon', 'all', (event) => {
try { try {
this.emit('keyDown', event.note.number) this.emit('keyDown', event.note.number + 12)
} catch(e){ } catch(e){
console.warn(e) console.warn(e)
} }
}) })
inputDevice.addListener('noteoff', 'all', (event) => { inputDevice.addListener('noteoff', 'all', (event) => {
try { try {
this.emit('keyUp', event.note.number) this.emit('keyUp', event.note.number + 12)
} catch(e){ } catch(e){
console.warn(e) console.warn(e)
} }

View File

@ -91,7 +91,7 @@
font-family: $font-family; font-family: $font-family;
font-size: 40px; font-size: 40px;
line-height: 30px; line-height: 30px;
margin: 20px 0px 30px 0px; margin: 130px 0px 50px 0px;
position: relative; position: relative;
} }
@ -143,17 +143,38 @@
} }
} }
#blurb { #germanAbout {
position: relative; position: relative;
text-transform: none; text-transform: none;
margin-top: 50px; margin-top: 50px;
font-size: 14px; font-size: 18px;
line-height: 25px; line-height: 25px;
a {
color: $orange;
} }
#englishAbout {
position: relative;
text-transform: none;
margin-top: 10px;
font-size: 18px;
line-height: 25px;
font-style: italic;
}
#germanMadeBy {
position: relative;
text-transform: none;
margin-top: 50px;
font-size: 18px;
line-height: 25px;
}
#englishMadeBy {
position: relative;
text-transform: none;
margin-top: 10px;
font-size: 18px;
line-height: 25px;
font-style: italic;
} }
} }

View File

@ -42,7 +42,7 @@ $topMargin: 35px;
font-weight: 300; font-weight: 300;
} }
#subTitle, #howItWorks { #germanSubTitle, #germanHowItWorks {
margin-top: $topMargin; margin-top: $topMargin;
letter-spacing: 0.8px; letter-spacing: 0.8px;
line-height: 30px; line-height: 30px;
@ -53,25 +53,66 @@ $topMargin: 35px;
text-align: center; text-align: center;
font-weight: 300; font-weight: 300;
} }
#subTitle {
margin-top: 0px;
letter-spacing: 0.8px;
line-height: 30px;
font-size: 15px;
width: 100%;
margin-left: auto;
margin-right: auto;
text-align: center;
font-weight: 300;
font-style: italic;
}
#howItWorks { #germanHowItWorks {
$size : 20px; $size : 20px;
$margin: 30px; $margin: 30px;
color: $orange;
width: 100&;
display: inline-block;
text-align: center;
/*margin-left: $margin;*/
}
#howItWorks {
$size : 20px;
$margin: 0px;
color: $orange;
width: 100%;
display: inline-block;
/*margin-left: $margin;*/
letter-spacing: 0.8px;
line-height: 30px;
font-size: 15px;
/*margin-right: auto;*/
text-align: center;
font-weight: 300;
font-style: italic;
}
#aboutPageLink {
$size : 20px;
$margin: 30px;
margin-top: $topMargin;
color: $orange; color: $orange;
width: auto; width: auto;
display: inline-block; display: inline-block;
margin-left: $margin; margin-left: $margin;
&:before { /*&:before {*/
position: absolute; /*position: absolute;*/
width: $size; /*width: $size;*/
height: $size; /*height: $size;*/
margin-left: -$margin; /*margin-left: -$margin;*/
margin-top: $size / 4; /*margin-top: $size / 4;*/
content : ''; /*content : '';*/
background-image: url(../images/yellow_play_triangle.svg); /*background-image: url(../images/yellow_play_triangle.svg);*/
} /*}*/
cursor: pointer; cursor: pointer;
@ -82,8 +123,8 @@ $topMargin: 35px;
} }
} }
$loaderWidth: 200px; $loaderWidth: 398px;
$loaderHeight: 60px; $loaderHeight: 118px;
#loader { #loader {
position: relative; position: relative;
@ -96,25 +137,6 @@ $topMargin: 35px;
margin-right: auto; margin-right: auto;
text-transform: uppercase; text-transform: uppercase;
&.clickable {
cursor: pointer;
transition: transform 0.1s;
&:hover {
transform: scale(1.1);
}
#fillText:active {
color: black!important;
background-color: $orange;
#piano {
filter: brightness(0);
}
}
}
#loaderText { #loaderText {
position: absolute; position: absolute;
width: 100%; width: 100%;
@ -126,7 +148,7 @@ $topMargin: 35px;
#fill { #fill {
position: absolute; position: absolute;
height: 100%; height: 100%;
width: 0%; width: 100%;
overflow: hidden; overflow: hidden;
background-color: black; background-color: black;
@ -135,7 +157,6 @@ $topMargin: 35px;
width: $loaderWidth; width: $loaderWidth;
height: 100%; height: 100%;
color: $orange; color: $orange;
$imgWidth: 40px; $imgWidth: 40px;
$margin : 52px; $margin : 52px;
@ -145,17 +166,26 @@ $topMargin: 35px;
} }
#play { #play {
margin-left: 150px;
right: $margin; right: $margin;
position: relative;
/*display: inline-block;*/
}
#germanPlay {
margin-left: 120px;
right: $margin;
position: absolute;
padding-top: 30px;
/*display: inline-block;*/
} }
#piano { #piano {
left: $margin; width: 100%;
width: $imgWidth;
height: 100%; height: 100%;
background-image : url(../images/keyboard_icon.svg); background-image : url(../images/keyboard_icon_CMedit.svg);
background-position: center center; background-position: center center;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
} }
} }
@ -165,6 +195,24 @@ $topMargin: 35px;
text-align: center; text-align: center;
font-weight: normal; font-weight: normal;
} }
&.loaded {
margin-top: $topMargin;
border: none;
width: 400px;
height: 120px;
background-color: transparent;
#loaderText {
background-color: transparent;
}
#fillText {
background-color: transparent;
}
#fill {
background-color: transparent;
}
}
} }
} }
@ -223,12 +271,12 @@ $topMargin: 35px;
#aiExperiments { #aiExperiments {
background-image: url(../images/badgeAI_master.svg); background-image: url(../images/badgeAI_master.svg);
&:hover { /*&:hover {*/
opacity: 1; /*opacity: 1;*/
&:active { /*&:active {*/
opacity: 0.3; /*opacity: 0.3;*/
} /*}*/
} /*}*/
} }
#googleFriends { #googleFriends {
@ -245,9 +293,9 @@ $topMargin: 35px;
margin-right: 0px; margin-right: 0px;
color: white; color: white;
&:hover { /*&:hover {*/
opacity: 1; /*opacity: 1;*/
} /*}*/
@media (max-width: $smallScreen) { @media (max-width: $smallScreen) {
font-size: 7px; font-size: 7px;
@ -290,10 +338,10 @@ $topMargin: 35px;
span { span {
color: $orange; color: $orange;
text-decoration: underline; text-decoration: underline;
cursor: pointer; /*cursor: pointer;*/
&:hover:active { /*&:hover:active {*/
color: white; /*color: white;*/
} /*}*/
} }
} }
@ -352,15 +400,15 @@ $topMargin: 35px;
a { a {
text-decoration: none; text-decoration: none;
cursor: pointer; /*cursor: pointer;*/
&:hover { /*&:hover {*/
opacity: 1; /*opacity: 1;*/
&:active { /*&:active {*/
opacity: 0.3; /*opacity: 0.3;*/
} /*}*/
} /*}*/
} }
} }
} }