├── public
├── music
│ └── .keep
├── fonts
│ ├── MaterialIcons-Regular.eot
│ ├── MaterialIcons-Regular.ttf
│ ├── MaterialIcons-Regular.woff
│ └── MaterialIcons-Regular.woff2
├── images
│ └── background.jpg
└── lovecall.html
├── src
├── css
│ ├── fonts
│ ├── images
│ ├── _reset-minimal.css
│ ├── _dimens.css
│ ├── _call.css
│ ├── _mixins.css
│ ├── index.css
│ ├── _about.css
│ ├── _song-loading.css
│ ├── _content.css
│ ├── _config.css
│ ├── _song-selector.css
│ ├── _transport.css
│ ├── _navigation.css
│ ├── _font.css
│ └── _metadata.css
├── misc
│ └── empty.opus
├── js
│ ├── ui
│ │ ├── container.js
│ │ ├── about.js
│ │ ├── config.js
│ │ ├── index.js
│ │ ├── song-selector.js
│ │ ├── song-loading.js
│ │ ├── dpi.js
│ │ ├── images.js
│ │ ├── frame.js
│ │ ├── metadata.js
│ │ └── navigation.js
│ ├── provider
│ │ ├── resize-detector.js
│ │ ├── font-selector.js
│ │ ├── mouseevent.js
│ │ ├── song.js
│ │ └── choreography.js
│ ├── util
│ │ └── data-helper.js
│ ├── data
│ │ ├── susutomo.js
│ │ ├── snowhare.js
│ │ ├── start-dash.js
│ │ ├── bokuima.js
│ │ ├── kiseki.js
│ │ └── nbg.js
│ ├── main.js
│ ├── init.js
│ ├── choreography
│ │ ├── manager.js
│ │ ├── tempo.js
│ │ └── metronome.js
│ ├── update.js
│ ├── conf.js
│ └── engine
│ │ └── audio-compat.js
├── templates
│ ├── song-loading.tmpl.html
│ ├── config.tmpl.html
│ ├── about.tmpl.html
│ ├── song-selector.tmpl.html
│ └── index.tmpl.html
└── images
│ ├── d.svg
│ ├── k.svg
│ ├── hi.svg
│ ├── oh.svg
│ ├── fu.svg
│ ├── sj.svg
│ ├── _original
│ ├── special.svg
│ ├── ld.svg
│ ├── qh.svg
│ ├── sj.svg
│ ├── hh.svg
│ ├── jump.svg
│ ├── kh.svg
│ ├── lt.svg
│ ├── clap.svg
│ ├── fu.svg
│ ├── hi.svg
│ ├── fuwa.svg
│ ├── d.svg
│ ├── k.svg
│ └── oh.svg
│ ├── ld.svg
│ ├── jump.svg
│ ├── clap.svg
│ └── hh.svg
├── Makefile
├── dev-server.sh
├── lovecall.config.example.js
├── plugins
├── android.json
└── fetch.json
├── config.xml
├── bower.json
├── README.md
├── .gitignore
├── package.json
└── webpack.config.js
/public/music/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/css/fonts:
--------------------------------------------------------------------------------
1 | ../../public/fonts
--------------------------------------------------------------------------------
/src/css/images:
--------------------------------------------------------------------------------
1 | ../../public/images
--------------------------------------------------------------------------------
/src/css/_reset-minimal.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
--------------------------------------------------------------------------------
/src/misc/empty.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoveCallProject/lovecall-frontend/HEAD/src/misc/empty.opus
--------------------------------------------------------------------------------
/public/fonts/MaterialIcons-Regular.eot:
--------------------------------------------------------------------------------
1 | ../../bower_components/material-design-icons/iconfont/MaterialIcons-Regular.eot
--------------------------------------------------------------------------------
/public/fonts/MaterialIcons-Regular.ttf:
--------------------------------------------------------------------------------
1 | ../../bower_components/material-design-icons/iconfont/MaterialIcons-Regular.ttf
--------------------------------------------------------------------------------
/public/fonts/MaterialIcons-Regular.woff:
--------------------------------------------------------------------------------
1 | ../../bower_components/material-design-icons/iconfont/MaterialIcons-Regular.woff
--------------------------------------------------------------------------------
/public/fonts/MaterialIcons-Regular.woff2:
--------------------------------------------------------------------------------
1 | ../../bower_components/material-design-icons/iconfont/MaterialIcons-Regular.woff2
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: webpack
2 |
3 | webpack:
4 | @./node_modules/.bin/webpack -p --devtool='#sourcemap'
5 |
6 | .PHONY: webpack
7 |
--------------------------------------------------------------------------------
/public/images/background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoveCallProject/lovecall-frontend/HEAD/public/images/background.jpg
--------------------------------------------------------------------------------
/src/css/_dimens.css:
--------------------------------------------------------------------------------
1 | $md-sidenav-locked-open-width: 320px;
2 |
3 | $toolbar-height: 8rem;
4 |
5 | $metadata-height: 7rem;
6 |
--------------------------------------------------------------------------------
/dev-server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # without this webpack-cordova-plugin will override the content base
4 | export BUILD_CORDOVA=0
5 |
6 | exec webpack-dev-server --inline --compress --content-base=public/ --devtool='#cheap-module-eval-source-map' $@
7 |
--------------------------------------------------------------------------------
/src/css/_call.css:
--------------------------------------------------------------------------------
1 | .call__canvas-container {
2 | width: 100%;
3 | height: 201px; /* keep this in sync with dimensions in js/ui/call.js */
4 | position: relative;
5 |
6 | > canvas {
7 | width: 100%;
8 | height: 100%;
9 |
10 | position: absolute;
11 | left: 0;
12 | top: 0;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/css/_mixins.css:
--------------------------------------------------------------------------------
1 | @define-mixin sidenav-locked-open-aware {
2 | &.sidenav-locked-open {
3 | @mixin-content;
4 | }
5 | }
6 |
7 |
8 | @define-mixin reserve-space-for-locked-sidenav {
9 | @mixin sidenav-locked-open-aware {
10 | padding-left: $md-sidenav-locked-open-width;
11 | }
12 | }
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lovecall.config.example.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // 远程音乐文件 URL 前缀, 一定要以斜线结束
3 | // URL prefix for remote music files, MUST end with a slash
4 | remoteMusicPrefix: 'music/',
5 | // 远程专辑封面 URL 前缀, 也要以斜线结束
6 | // URL prefix for remote cover art files, also MUST end with a slash
7 | remoteCoverArtPrefix: 'music/cover/',
8 | };
9 |
--------------------------------------------------------------------------------
/src/css/index.css:
--------------------------------------------------------------------------------
1 | @import 'dimens';
2 | @import 'mixins';
3 |
4 | @import 'reset-minimal';
5 | @import 'font';
6 | @import 'navigation';
7 | @import 'content';
8 | @import 'metadata';
9 | @import 'call';
10 | @import 'transport';
11 | @import 'song-loading';
12 | @import 'song-selector';
13 | @import 'about';
14 | @import 'config';
15 |
--------------------------------------------------------------------------------
/src/css/_about.css:
--------------------------------------------------------------------------------
1 | .about__toolbar__icon {
2 | margin: 1rem;
3 | }
4 |
5 | .about__content__listitem {
6 | display: flex;
7 | align-items: center;
8 | }
9 |
10 | .about {
11 | width: 50rem;
12 | height: 60rem;
13 | display: flex;
14 | }
15 |
16 | .about__content {
17 | flex: 1;
18 | md-tabs {
19 | height: 100%;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/css/_song-loading.css:
--------------------------------------------------------------------------------
1 | .song-loading {
2 | align-items: center;
3 | }
4 |
5 | .song-loading__progress {
6 | flex: 0 0 auto;
7 | }
8 |
9 | .song-loading__message {
10 | flex: 1;
11 |
12 | font-weight: normal;
13 | font-size: 1rem;
14 | }
15 |
16 | .song-loading__actions {
17 | /* XXX: why the f**k is this necessary */
18 | order: 2;
19 | }
20 |
--------------------------------------------------------------------------------
/src/js/ui/container.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 | require('angular-material');
6 |
7 |
8 | var mod = angular.module('lovecall/ui/container', ['ngMaterial']);
9 |
10 | mod.controller('ContainerController', function($scope, $mdMedia) {
11 | $scope.$mdMedia = $mdMedia;
12 | });
13 |
--------------------------------------------------------------------------------
/src/css/_content.css:
--------------------------------------------------------------------------------
1 | body {
2 | display: flex;
3 | flex-direction: column;
4 | min-height: 100vh;
5 | }
6 |
7 | .content-container {
8 | @mixin reserve-space-for-locked-sidenav;
9 |
10 | flex: 1;
11 | padding-top: $metadata-height / 2;
12 | background-image: url('images/background.jpg');
13 | background-size: cover;
14 | }
15 |
16 | .main-container {
17 | width: 100%;
18 | position: relative;
19 | flex: 1;
20 | padding: 8px;
21 | }
22 |
--------------------------------------------------------------------------------
/plugins/android.json:
--------------------------------------------------------------------------------
1 | {
2 | "prepare_queue": {
3 | "installed": [],
4 | "uninstalled": []
5 | },
6 | "config_munge": {
7 | "files": {}
8 | },
9 | "installed_plugins": {
10 | "cordova-plugin-whitelist": {
11 | "PACKAGE_NAME": "moe.lovecall.android"
12 | },
13 | "cordova-plugin-fileopener": {
14 | "PACKAGE_NAME": "moe.lovecall.android"
15 | }
16 | },
17 | "dependent_plugins": {}
18 | }
--------------------------------------------------------------------------------
/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | LoveCall
4 |
5 | LoveCall Android app
6 |
7 |
8 | LoveCallProject
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/plugins/fetch.json:
--------------------------------------------------------------------------------
1 | {
2 | "cordova-plugin-whitelist": {
3 | "source": {
4 | "type": "registry",
5 | "id": "cordova-plugin-whitelist"
6 | },
7 | "is_top_level": true,
8 | "variables": {}
9 | },
10 | "cordova-plugin-fileopener": {
11 | "source": {
12 | "type": "registry",
13 | "id": "cordova-plugin-fileopener"
14 | },
15 | "is_top_level": true,
16 | "variables": {}
17 | }
18 | }
--------------------------------------------------------------------------------
/src/css/_config.css:
--------------------------------------------------------------------------------
1 | .config {
2 | min-width: 500px;
3 | }
4 |
5 | .config__toolbar__icon {
6 | margin: 0px 10px 0px 10px;
7 | }
8 |
9 | .config__content {
10 | padding: 10px;
11 | overflow: hidden;
12 | }
13 |
14 | .config__content__value {
15 | font-weight: bold;
16 | }
17 |
18 | .config__preference--switch {
19 | display: flex;
20 | align-items: center;
21 | }
22 |
23 | .config__preference__desc {
24 | flex: 1 0 auto;
25 | }
26 |
27 | .config__submit {
28 | float: right;
29 | }
30 |
--------------------------------------------------------------------------------
/src/js/provider/resize-detector.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 | var elementResizeDetector = require('element-resize-detector');
6 |
7 |
8 | var mod = angular.module('lovecall/provider/resize-detector', [
9 | ]);
10 |
11 | mod.factory('ResizeDetector', function() {
12 | return elementResizeDetector();
13 | });
14 | /* @license-end */
15 |
16 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
17 |
--------------------------------------------------------------------------------
/src/css/_song-selector.css:
--------------------------------------------------------------------------------
1 | .song-selector {
2 | width: 50rem;
3 | max-height: 80rem;
4 | }
5 |
6 | .md-dialog-fullscreen {
7 | max-height: 100vh;
8 | }
9 |
10 | .toolbar__icon {
11 | margin: 1rem;
12 | }
13 |
14 | .toolbar__space {
15 | flex: 1;
16 | }
17 |
18 | .song-selector__content {
19 | max-height: 600px;
20 | }
21 |
22 | .song-selector__content__button {
23 | text-align: start;
24 | text-transform: none;
25 | width: 100%;
26 | margin: 0;
27 | border-radius: 0;
28 | .md-ripple-container {
29 | border-radius: 0;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lovecall-frontend",
3 | "version": "0.0.1",
4 | "homepage": "https://github.com/LoveCallProject/lovecall-frontend",
5 | "authors": [
6 | "xen0n"
7 | ],
8 | "description": "Frontend to LoveCall",
9 | "keywords": [
10 | "lovecall",
11 | "frontend"
12 | ],
13 | "license": "GPL-3",
14 | "ignore": [
15 | "**/.*",
16 | "build",
17 | "node_modules",
18 | "bower_components",
19 | "test",
20 | "tests"
21 | ],
22 | "dependencies": {
23 | "angular-logex": "~0.0.10",
24 | "material-design-icons": "~2.1.1"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/templates/song-loading.tmpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 | {{message}}
10 |
11 |
12 |
13 | 好吧
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/css/_transport.css:
--------------------------------------------------------------------------------
1 | #transport {
2 | margin-top:2rem;
3 | width: 100%;
4 | position: absolute;
5 | bottom: 0;
6 | left: 0;
7 |
8 | /* padding is necessary because position is absolute */
9 | padding: 0 8px 8px;
10 | }
11 |
12 | .transport__canvas-container {
13 | width: 100%;
14 | height: 3rem;
15 |
16 | > canvas {
17 | width: 100%;
18 | height: 100%;
19 | cursor: pointer;
20 | }
21 | }
22 |
23 | #transport__play {
24 | display: flex;
25 | margin: auto;
26 | }
27 |
28 | .transport__volume {
29 | display: flex;
30 | flex-direction: row;
31 | position: absolute;
32 | bottom: 0;
33 | right: 2rem;
34 | md-slider {
35 | width: 5rem;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/js/ui/about.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 | require('angular-material');
6 |
7 | require('../conf');
8 |
9 |
10 | var mod = angular.module('lovecall/ui/about', [
11 | 'ngMaterial',
12 | 'lovecall/conf',
13 | ]);
14 |
15 | mod.controller('AboutDialogController', function($scope, $mdDialog, LCConfig) {
16 | $scope.version = LCConfig.VERSION + ' (' + LCConfig.HASH + ')';
17 |
18 | $scope.contributors = [
19 | {
20 | name: 'xen0n'
21 | },
22 | {
23 | name: 'disoul'
24 | },
25 | {
26 | name: 'fakedestinyck'
27 | }
28 | ]
29 |
30 | $scope.close = function() {
31 | $mdDialog.cancel();
32 | }
33 | });
34 |
--------------------------------------------------------------------------------
/src/js/util/data-helper.js:
--------------------------------------------------------------------------------
1 | // modified from SO answer http://stackoverflow.com/a/5100158/596531
2 | module.exports.dataUriToArray = function(dataURI) {
3 | // convert base64/URLEncoded data component to raw binary data held in a string
4 | var byteString;
5 | if (dataURI.split(',')[0].indexOf('base64') >= 0)
6 | byteString = atob(dataURI.split(',')[1]);
7 | else
8 | byteString = unescape(dataURI.split(',')[1]);
9 |
10 | // separate out the mime component
11 | var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
12 |
13 | // write the bytes of the string to a typed array
14 | var ia = new Uint8Array(byteString.length);
15 | for (var i = 0; i < byteString.length; i++) {
16 | ia[i] = byteString.charCodeAt(i);
17 | }
18 |
19 | return ia;
20 | };
21 |
--------------------------------------------------------------------------------
/src/css/_navigation.css:
--------------------------------------------------------------------------------
1 | #sidenav {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | .md-button,.md-ripple-container{
6 | border-radius: 0;
7 | }
8 | }
9 |
10 | md-icon.inline {
11 | display: inline-block;
12 | }
13 |
14 | .hidden-button {
15 | visibility: hidden;
16 | }
17 |
18 | .hidden-android {
19 | display: none;
20 | }
21 |
22 | .line-button {
23 | width: 100%;
24 | margin: 0;
25 | padding: 0.3rem 0 0.3rem 1rem;
26 | text-align: left;
27 | font-size: 18px;
28 | md-icon {
29 | font-size: 30px;
30 | }
31 | }
32 |
33 | .toolbar-container {
34 | height: $toolbar-height;
35 | position: relative;
36 | /* TODO */
37 | background: #333;
38 | overflow-y: visible;
39 | }
40 |
41 | #toolbar {
42 | background: none;
43 |
44 | @mixin sidenav-locked-open-aware {
45 | display: none;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LoveCall
2 |
3 | [](https://david-dm.org/LoveCallProject/lovecall-frontend#info=devDependencies)
4 |
5 | TODO
6 |
7 |
8 | ## License
9 |
10 | * GPLv3+ (see the `COPYING` file for the full text)
11 |
12 |
13 | ## Hacking
14 |
15 | ```sh
16 | # install build and dev tools
17 | npm install -g bower webpack webpack-dev-server
18 |
19 | # install local deps
20 | # material-design-icons is *large*, please be patient and make sure to have
21 | # enough free space
22 | npm install
23 | bower install
24 |
25 | # to build
26 | webpack # -p for production build
27 |
28 | # to fire up the dev server
29 | webpack-dev-server --inline --compress --content-base=public/
30 |
31 | # invoke webpack with webpack-cordova-plugin enabled (instructions TODO)
32 | BUILD_CORDOVA=1 webpack
33 | ```
34 |
--------------------------------------------------------------------------------
/src/js/ui/config.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 | require('angular-material');
6 |
7 | require('../conf');
8 |
9 |
10 | var mod = angular.module('lovecall/ui/config', [
11 | 'ngMaterial',
12 | 'lovecall/conf',
13 | ]);
14 |
15 | mod.controller('ConfigDialogController', function($scope, $mdDialog, LCConfig) {
16 | $scope.bufferSizeOrder = LCConfig.getAudioBufferSizeOrder();
17 | $scope.useRomaji = LCConfig.isRomajiEnabled();
18 |
19 |
20 | $scope.$watch('bufferSizeOrder', function(to, from) {
21 | if (to === from) {
22 | return;
23 | }
24 |
25 | LCConfig.setAudioBufferSizeOrder(to);
26 | });
27 |
28 |
29 | $scope.$watch('useRomaji', function(to, from) {
30 | if (to === from) {
31 | return;
32 | }
33 |
34 | LCConfig.setRomajiEnabled(to);
35 | });
36 |
37 |
38 | $scope.close = function() {
39 | $mdDialog.cancel();
40 | }
41 | });
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # for obvious copyright reasons
2 | *.mp3
3 | *.ogg
4 | *.wav
5 | *.opus
6 |
7 | /public/music/cover/
8 |
9 | # site config
10 | /lovecall.config.js
11 |
12 | # build intermediates
13 | /build
14 |
15 | # dependencies
16 | /bower_components
17 |
18 |
19 | # Node.gitignore
20 | # Logs
21 | logs
22 | *.log
23 | npm-debug.log*
24 |
25 | # Runtime data
26 | pids
27 | *.pid
28 | *.seed
29 |
30 | # Directory for instrumented libs generated by jscoverage/JSCover
31 | lib-cov
32 |
33 | # Coverage directory used by tools like istanbul
34 | coverage
35 |
36 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
37 | .grunt
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (http://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directory
46 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
47 | node_modules
48 |
49 | # Optional npm cache directory
50 | .npm
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
--------------------------------------------------------------------------------
/src/js/ui/index.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 |
6 | require('./about');
7 | require('./container');
8 | require('./config.js');
9 | require('./call');
10 | require('./frame');
11 | require('./metadata');
12 | require('./navigation');
13 | require('./song-loading');
14 | require('./song-selector');
15 | require('./transport');
16 |
17 | require('../../templates/index.tmpl.html');
18 |
19 |
20 | var mod = angular.module('lovecall/ui/index', [
21 | 'lovecall/ui/about',
22 | 'lovecall/ui/container',
23 | 'lovecall/ui/config',
24 | 'lovecall/ui/call',
25 | 'lovecall/ui/frame',
26 | 'lovecall/ui/metadata',
27 | 'lovecall/ui/navigation',
28 | 'lovecall/ui/song-loading',
29 | 'lovecall/ui/song-selector',
30 | 'lovecall/ui/transport'
31 | ]);
32 |
33 | mod.directive('lovecallApp', function() {
34 | return {
35 | restrict: 'EA',
36 | templateUrl: 'index.tmpl.html',
37 | };
38 | });
39 | /* @license-end */
40 |
41 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
42 |
--------------------------------------------------------------------------------
/src/js/ui/song-selector.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | var _ = require('lodash');
5 |
6 | require('angular');
7 | require('angular-material');
8 |
9 | require('../provider/font-selector');
10 |
11 |
12 | var mod = angular.module('lovecall/ui/song-selector', [
13 | 'ngMaterial',
14 | 'lovecall/provider/choreography',
15 | 'lovecall/provider/font-selector',
16 | ]);
17 |
18 | mod.controller('SongSelectorController', function($scope, $mdDialog, Choreography, FontSelector) {
19 |
20 | var init = function() {
21 | var songs = Choreography.getAvailableSongs();
22 | var fontFamilies = _(songs)
23 | .map('lang')
24 | .uniq()
25 | .transform(function(result, v) {
26 | result[v] = FontSelector.fontFamilyForLanguage(v);
27 | }, {})
28 | .value();
29 |
30 | $scope.songs = songs;
31 | $scope.fontFamilies = fontFamilies;
32 | };
33 |
34 | $scope.close = function() {
35 | $mdDialog.cancel();
36 | }
37 |
38 | $scope.answer = function(answer) {
39 | $mdDialog.hide(answer);
40 | }
41 |
42 | init();
43 | });
44 |
--------------------------------------------------------------------------------
/src/css/_font.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Material Icons';
3 | font-style: normal;
4 | font-weight: 400;
5 | src: url(fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
6 | src: local('Material Icons'),
7 | local('MaterialIcons-Regular'),
8 | url(fonts/MaterialIcons-Regular.woff2) format('woff2'),
9 | url(fonts/MaterialIcons-Regular.woff) format('woff'),
10 | url(fonts/MaterialIcons-Regular.ttf) format('truetype');
11 | }
12 |
13 | .material-icons {
14 | font-family: 'Material Icons';
15 | font-weight: normal;
16 | font-style: normal;
17 | font-size: 24px; /* Preferred icon size */
18 | display: inline-block;
19 | width: 1em;
20 | height: 1em;
21 | line-height: 1;
22 | text-transform: none;
23 | letter-spacing: normal;
24 | word-wrap: normal;
25 | white-space: nowrap;
26 | direction: ltr;
27 |
28 | /* Support for all WebKit browsers. */
29 | -webkit-font-smoothing: antialiased;
30 | /* Support for Safari and Chrome. */
31 | text-rendering: optimizeLegibility;
32 |
33 | /* Support for Firefox. */
34 | -moz-osx-font-smoothing: grayscale;
35 |
36 | /* Support for IE. */
37 | font-feature-settings: 'liga';
38 | }
39 |
--------------------------------------------------------------------------------
/src/js/provider/font-selector.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 |
3 | 'use strict';
4 |
5 | require('angular');
6 |
7 |
8 | var BUILTIN_FONT_FAMILIES = {
9 | en: '"Source Sans Pro", "DejaVu Sans", "Bitstream Vera", sans-serif',
10 | cmn: '"Source Han Sans CN", "Source Han Sans SC", "思源黑体 CN", "思源黑体", "Noto Sans CJK SC", "微软雅黑", "Microsoft YaHei", sans-serif',
11 | ja: '"Source Han Sans JP", "源ノ角ゴシック", "Noto Sans CJK JP", "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro",Osaka, "メイリオ", Meiryo, "MS Pゴシック", "MS PGothic", sans-serif',
12 | };
13 |
14 |
15 | var mod = angular.module('lovecall/provider/font-selector', [
16 | ]);
17 |
18 | mod.factory('FontSelector', function() {
19 | this.fontFamilyForLanguage = function(language) {
20 | var result = BUILTIN_FONT_FAMILIES[language];
21 | return typeof(result) !== 'undefined' ? result : 'sans-serif';
22 | };
23 |
24 |
25 | this.canvasFontForLanguage = function(language, size) {
26 | return size + 'px ' + this.fontFamilyForLanguage(language);
27 | };
28 |
29 |
30 | return this;
31 | });
32 | /* @license-end */
33 |
34 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
35 |
--------------------------------------------------------------------------------
/src/templates/config.tmpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | settings
8 |
9 | 设置
10 |
11 |
12 | close
13 |
14 |
15 |
16 |
17 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/public/lovecall.html:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
21 |
22 | ラブコール
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/js/data/susutomo.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "lovecall": 0,
3 | "metadata": {
4 | "song": {
5 | "title": "ススメ→トゥモロウ",
6 | "artist": "μ's",
7 | "album": "ススメ→トゥモロウ/START:DASH!!",
8 | "remoteBasename": "susutomo",
9 | "sources": {
10 | "fallback:": {
11 | "offset": 5134
12 | },
13 | "md5:c58b2892e24d0239a6f31b769c230dac": {
14 | "offset": 5134
15 | }
16 | },
17 | "timing": [
18 | [0, 100.0, 4, 4, 0],
19 | [21178, 190.0, 4, 4, 9]
20 | ]
21 | },
22 | "palette": [
23 | "#ffa500",
24 | "#eeeeee",
25 | "#0000ff"
26 | ]
27 | },
28 | "form": [
29 | [0, 0, 9, 0, "I"],
30 | [9, 0, 25, 0, "G0"],
31 | [25, 0, 43, 0, "A1"],
32 | [43, 0, 61, 0, "B1"],
33 | [53, 0, 71, 0, "C1"],
34 | [69, 0, 80, 0, "G1"],
35 | [80, 0, 97, 0, "A2"],
36 | [97, 0, 107, 0, "B2"],
37 | [107, 0, 125, 0, "C2"],
38 | [125, 0, 132, 0, "G2"],
39 | [132, 0, 148, 0, "S1"],
40 | [148, 0, 167, 0, "G3"],
41 | [167, 0, 176, 0, "S2"],
42 | [176, 0, 190, 0, "C3"],
43 | [190, 0, -1, -1, "O"]
44 | ],
45 | "colors": [
46 | [0, 0, -1, -1, -1]
47 | ],
48 | "timeline": [
49 | [1, 0, 0, 16, 0, "上举", 16]
50 | ]
51 | };
52 |
--------------------------------------------------------------------------------
/src/js/ui/song-loading.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 | require('angular-material');
6 |
7 | var mod = angular.module('lovecall/ui/song-loading', [
8 | 'ngMaterial',
9 | ]);
10 |
11 | mod.controller('SongLoadingController', function($scope, $timeout, $mdDialog) {
12 | $scope.message = '歌曲加载中…';
13 | $scope.progressVisibility = 'visible';
14 | $scope.loadFailed = false;
15 |
16 |
17 | var closeDialog = function() {
18 | $mdDialog.hide(null);
19 | };
20 |
21 |
22 | $scope.closeDialog = closeDialog;
23 |
24 |
25 | $scope.$on('audio:decoding', function(e) {
26 | $scope.message = '音频解码中…(移动设备下可能非常缓慢,请耐心等候)';
27 | });
28 |
29 |
30 | $scope.$on('audio:loadFailed', function(e) {
31 | $scope.message = '音频解码失败,请刷新重试或更换浏览器';
32 | $scope.loadFailed = true;
33 | });
34 |
35 |
36 | $scope.$on('song:hideLoadingDialog', function(e, errored) {
37 | // hide progress indicator
38 | $scope.progressVisibility = 'hidden';
39 |
40 | if (!errored) {
41 | $scope.message = '歌曲加载完成';
42 | $timeout(closeDialog, 1000);
43 | } else {
44 | $scope.message = '歌曲加载失败';
45 | $scope.loadFailed = true;
46 | }
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/src/js/main.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 | require('angular-animate');
6 | require('angular-aria');
7 | require('angular-material');
8 | require('angular-logex');
9 | require('angular-local-storage');
10 |
11 | require('./ui/index');
12 | require('./init');
13 | require('./update.js');
14 |
15 | require('../../node_modules/angular-material/angular-material.css');
16 | require('../css/index.css');
17 |
18 | var mod = angular.module('lovecall/main', [
19 | 'lovecall/init',
20 | 'lovecall/update',
21 | 'lovecall/ui/index',
22 | 'log.ex.uo',
23 | 'LocalStorageModule',
24 | ]);
25 |
26 | mod.config(function(logExProvider) {
27 | logExProvider.enableLogging(true);
28 |
29 | logExProvider.overrideLogPrefix(function(className) {
30 | var timeFrag = '[' + new Date().toISOString() + '] ';
31 | var classFrag = angular.isString(className) ? '[' + className + '] ' : '';
32 |
33 | return timeFrag + classFrag;
34 | });
35 | });
36 |
37 | mod.config(function(localStorageServiceProvider) {
38 | localStorageServiceProvider.setPrefix('lovecall.');
39 | });
40 |
41 | angular.bootstrap(angular.element(document.getElementById('appmount')), ['lovecall/main', 'ngMaterial']);
42 | /* @license-end */
43 |
44 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lovecall-frontend",
3 | "version": "0.0.1",
4 | "description": "Frontend to LoveCall",
5 | "private": true,
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/LoveCallProject/lovecall-frontend.git"
13 | },
14 | "keywords": [
15 | "lovecall",
16 | "frontend"
17 | ],
18 | "author": "xen0n",
19 | "license": "GPL-3",
20 | "bugs": {
21 | "url": "https://github.com/LoveCallProject/lovecall-frontend/issues"
22 | },
23 | "homepage": "https://github.com/LoveCallProject/lovecall-frontend",
24 | "devDependencies": {
25 | "angular": "^1.4.8",
26 | "angular-animate": "^1.4.8",
27 | "angular-aria": "^1.4.8",
28 | "angular-local-storage": "^0.2.2",
29 | "angular-material": "^1.0.1",
30 | "autoprefixer": "^6.3.1",
31 | "css-loader": "^0.23.1",
32 | "element-resize-detector": "^1.0.3",
33 | "file-loader": "^0.8.5",
34 | "html-webpack-plugin": "^1.7.0",
35 | "lodash": "^4.0.0",
36 | "ng-annotate-webpack-plugin": "^0.1.2",
37 | "ng-cache-loader": "0.0.15",
38 | "postcss-loader": "^0.8.0",
39 | "precss": "^1.4.0",
40 | "script-loader": "^0.6.1",
41 | "spark-md5": "^2.0.0",
42 | "style-loader": "^0.13.0",
43 | "url-loader": "^0.5.7",
44 | "webpack": "^1.12.11",
45 | "webpack-cordova-plugin": "^0.1.5"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/js/init.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 |
6 | require('./provider/choreography');
7 | require('./ui/frame');
8 |
9 | var mimimi = require('./data/mimimi');
10 | var snowhare = require('./data/snowhare');
11 | var wr = require('./data/wr');
12 | //var susutomo = require('./data/susutomo');
13 | var startDash = require('./data/start-dash');
14 | var nbg = require('./data/nbg');
15 | var bokuima = require('./data/bokuima');
16 | var kiseki = require('./data/kiseki');
17 |
18 | // easter egg
19 | var fdInnerOni = require('./data/fd-inner-oni');
20 |
21 |
22 | var mod = angular.module('lovecall/init', [
23 | 'lovecall/provider/choreography',
24 | 'lovecall/ui/frame'
25 | ]);
26 |
27 | mod.run(function($window, Choreography, FrameManager) {
28 | // load bundled call tables
29 | Choreography.loadTable(mimimi);
30 | Choreography.loadTable(snowhare);
31 | Choreography.loadTable(wr);
32 | Choreography.loadTable(bokuima);
33 | Choreography.loadTable(kiseki);
34 | //Choreography.loadTable(susutomo);
35 | Choreography.loadTable(startDash);
36 | Choreography.loadTable(nbg);
37 |
38 | // easter egg
39 | if (Math.random() < 0.1) {
40 | Choreography.loadTable(fdInnerOni);
41 | }
42 |
43 | // frame loop
44 | FrameManager.startFrameLoop($window);
45 | });
46 | /* @license-end */
47 |
48 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
49 |
--------------------------------------------------------------------------------
/src/templates/about.tmpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | info
8 |
9 | 关于
10 |
11 |
12 | close
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | LoveCall
21 | Version:{{version}}
22 |
23 |
24 |
25 |
26 |
27 |
31 |
32 | person
33 |
34 |
35 |
{{ contributor.name }}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/js/choreography/manager.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | var _ = require('lodash');
5 |
6 |
7 | var LoveCallTableManager = function() {
8 | this.tables = {};
9 | this.nextTableIdx = 0;
10 |
11 | this.hashCache = {};
12 | this.songCache = {};
13 | };
14 |
15 |
16 | LoveCallTableManager.prototype.registerTable = function(table) {
17 | var idx = this.nextTableIdx;
18 | this.tables[idx] = table;
19 | this.nextTableIdx += 1;
20 |
21 | var hashMap = _.transform(table.metadata.song.sources, function(result, v, k) {
22 | result[k.toLowerCase()] = idx;
23 | });
24 |
25 | _.extend(this.hashCache, hashMap);
26 |
27 | this.songCache[idx] = {
28 | ti: table.metadata.song.title,
29 | ar: table.metadata.song.artist,
30 | al: table.metadata.song.album,
31 | lang: table.metadata.song.lang,
32 | idx: idx
33 | };
34 |
35 | return idx;
36 | };
37 |
38 |
39 | LoveCallTableManager.prototype.lookupTable = function(idx, hash) {
40 | var lookupKey = hash.toLowerCase();
41 |
42 | if (hash === 'fallback:') {
43 | return this.tables[idx];
44 | }
45 |
46 | var tableByHash = this.tables[this.hashCache[lookupKey]];
47 | return typeof(tableByHash) !== 'undefined' ? tableByHash : this.tables[idx];
48 | };
49 |
50 |
51 | LoveCallTableManager.prototype.getAvailableSongs = function() {
52 | // return a copy
53 | return _.extend({}, this.songCache);
54 | }
55 |
56 |
57 | module.exports = {
58 | LoveCallTableManager: LoveCallTableManager
59 | };
60 | /* @license-end */
61 |
62 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
63 |
--------------------------------------------------------------------------------
/src/js/ui/dpi.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 |
6 |
7 | var mod = angular.module('lovecall/ui/dpi', []);
8 |
9 | mod.factory('DPIManager', function($window, $log) {
10 | $log = $log.getInstance('DPIManager');
11 |
12 | var devicePixelRatio = $window.devicePixelRatio || 1;
13 | var canvasBackingStoreRatio = (function() {
14 | var testCanvas = document.createElement('canvas');
15 | var ctx = testCanvas.getContext('2d');
16 | var result = (
17 | ctx.webkitBackingStorePixelRatio ||
18 | ctx.mozBackingStorePixelRatio ||
19 | ctx.msBackingStorePixelRatio ||
20 | ctx.oBackingStorePixelRatio ||
21 | ctx.backingStorePixelRatio ||
22 | 1
23 | );
24 | testCanvas = null;
25 | return result;
26 | })();
27 |
28 | var ratio = devicePixelRatio / canvasBackingStoreRatio;
29 |
30 |
31 | var scaleCanvas = function(canvas, ctx, w, h) {
32 | canvas.width = w * ratio;
33 | canvas.height = h * ratio;
34 |
35 | if (devicePixelRatio !== canvasBackingStoreRatio) {
36 | ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
37 | }
38 | };
39 |
40 |
41 | $log.info(
42 | 'devicePixelRatio',
43 | devicePixelRatio,
44 | 'canvasBackingStoreRatio',
45 | canvasBackingStoreRatio
46 | );
47 |
48 | return {
49 | devicePixelRatio: devicePixelRatio,
50 | canvasBackingStoreRatio: canvasBackingStoreRatio,
51 | ratio: ratio,
52 | scaleCanvas: scaleCanvas,
53 | };
54 | });
55 | /* @license-end */
56 |
57 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
58 |
--------------------------------------------------------------------------------
/src/js/ui/images.js:
--------------------------------------------------------------------------------
1 | var fu = require('../../images/fu.svg');
2 | var fuwa = require('../../images/fuwa.svg');
3 | var hh = require('../../images/hh.svg');
4 | var hi = require('../../images/hi.svg');
5 | var jump = require('../../images/jump.svg');
6 | var kh = require('../../images/kh.svg');
7 | var ld = require('../../images/ld.svg');
8 | var lt = require('../../images/lt.svg');
9 | var oh = require('../../images/oh.svg');
10 | var qh = require('../../images/qh.svg');
11 | var sj = require('../../images/sj.svg');
12 | var special = require('../../images/special.svg');
13 | var clap = require('../../images/clap.svg');
14 | var d = require('../../images/d.svg');
15 | var k = require('../../images/k.svg');
16 |
17 | var taicallImages = {};
18 |
19 |
20 | var makeImageObj = function(key, uri) {
21 | var result = new Image(100, 100);
22 | result.src = uri;
23 | result.onload = function() {
24 | console.log('builtin image onload');
25 | taicallImages[key] = result;
26 | };
27 | return result;
28 | };
29 |
30 |
31 | var objects = [
32 | makeImageObj('fu', fu),
33 | makeImageObj('fuwa', fuwa),
34 | makeImageObj('hh', hh),
35 | makeImageObj('hi', hi),
36 | makeImageObj('jump', jump),
37 | makeImageObj('kh', kh),
38 | makeImageObj('ld', ld),
39 | makeImageObj('lt', lt),
40 | makeImageObj('oh', oh),
41 | makeImageObj('qh', qh),
42 | makeImageObj('sj', sj),
43 | makeImageObj('special', special),
44 | makeImageObj('clap', clap),
45 | makeImageObj('d', d),
46 | makeImageObj('k', k),
47 | ];
48 |
49 | var taicallImagesCount = objects.length;
50 |
51 | module.exports = {
52 | taicall: taicallImages,
53 | taicallImagesCount: taicallImagesCount,
54 | };
55 |
--------------------------------------------------------------------------------
/src/css/_metadata.css:
--------------------------------------------------------------------------------
1 | #metadata {
2 | display: flex;
3 | flex-direction: row;
4 | z-index: 1;
5 | overflow: visible;
6 | position: absolute;
7 | bottom: -$metadata-height / 2;
8 | width: 100%;
9 | padding: 0 8px;
10 | }
11 |
12 | #metadata.sidenav-locked-open {
13 | padding-left: 8px + $md-sidenav-locked-open-width;
14 | }
15 |
16 | .metadata__image-container,
17 | .metadata__image {
18 | width: $metadata-height;
19 | height: $metadata-height;
20 | }
21 |
22 | .metadata__image-container {
23 | border-radius: 50%;
24 | overflow: hidden;
25 |
26 | background-color: #666;
27 | box-shadow: 0 2px 5px #000;
28 | }
29 |
30 | .metadata__image {
31 | background-size: 100%;
32 |
33 | /* very weird bug of Chromium */
34 | border-radius: 50%;
35 |
36 | animation-name: rotate;
37 | animation-iteration-count: infinite;
38 | animation-timing-function: linear;
39 | animation-play-state: paused;
40 | }
41 |
42 | @keyframes rotate {
43 | from {
44 | transform: rotate(0);
45 | }
46 | to {
47 | transform: rotate(360deg);
48 | }
49 | }
50 |
51 | .metadata__text {
52 | width: 100%;
53 | flex: 1;
54 | position: relative;
55 | }
56 |
57 | .metadata__text__title-container {
58 | position: absolute;
59 | bottom: 50%;
60 | left: 0;
61 | }
62 |
63 | .metadata__text__other {
64 | position: absolute;
65 | top: 50%;
66 | left: 0;
67 | }
68 |
69 | .metadata__text__other,
70 | .metadata__text__title-container {
71 | padding-left: 0.5rem;
72 | }
73 |
74 | .metadata__text__title {
75 | margin: 0;
76 | color: rgba(255, 255, 255, 0.87);
77 | }
78 |
79 | .metadata__text__other__artist {
80 | &:after {
81 | content: ' /';
82 | }
83 |
84 | &:empty:after {
85 | display: none;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/templates/song-selector.tmpl.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
11 | queue_music
12 |
13 | 选择想练习的歌曲
14 |
15 |
16 |
17 | close
18 |
19 |
20 |
21 |
22 |
23 |
24 |
32 |
35 |
39 | music_note
40 |
41 |
42 |
{{ song.ti }}
43 |
{{ song.ar }}
44 |
{{ song.al }}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/js/update.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 |
6 | require('./conf');
7 |
8 | var mod = angular.module('lovecall/update', [
9 | 'ngMaterial',
10 | 'lovecall/conf',
11 | ]);
12 |
13 | mod.run(function($http, $mdDialog, LCConfig) {
14 | // only check in cordova
15 | if (window.cordova !== undefined) {
16 | $http({
17 | method: 'GET',
18 | url: 'http://lovecall.moe/static/version.json'
19 | }).then(function successCallback(res) {
20 | var versionInfo = res.data;
21 | console.log(versionInfo);
22 | if (versionInfo.version > LCConfig.VERSION) {
23 | var confirm = $mdDialog.confirm()
24 | .title('LoveCall有新版本v' + versionInfo.version + ',是否更新')
25 | .textContent('更新内容:' + versionInfo.content)
26 | .ariaLabel('Lucky day')
27 | .ok('立即更新')
28 | .openFrom({
29 | top: document.body.height / 2,
30 | width: 30,
31 | height: 80
32 | })
33 | .closeTo({
34 | top: document.body.height / 2
35 | })
36 | .cancel('下次再说')
37 |
38 | $mdDialog.show(confirm).then(function() {
39 | window.cordova.plugins.FileOpener.openFile(
40 | LCConfig.REMOTE_APK_PREFIX + 'lovecall' + versionInfo.version +'.apk',
41 | function() {
42 | // success
43 | }
44 | );
45 |
46 | console.log("let's update");
47 | }, function() {});
48 | }
49 |
50 | }, function errorCallback(res) {
51 |
52 | });
53 | }
54 | });
55 | /* @license-end */
56 |
57 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
58 |
--------------------------------------------------------------------------------
/src/images/d.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/src/images/k.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/src/js/provider/mouseevent.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 |
6 |
7 | var mod = angular.module('lovecall/provider/mouseevent', [
8 | ]);
9 |
10 | mod.factory('MouseEvent', function($window) {
11 | var mouseEventCallbackHandlerFactory = function(eventName) {
12 | var listeners = {};
13 | var id = 0;
14 |
15 | var callback = function(e) {
16 | for (var k in listeners) {
17 | listeners[k](e);
18 | }
19 | };
20 |
21 |
22 | var add = function(callback) {
23 | var usedId = id;
24 | listeners[usedId] = callback;
25 | id += 1;
26 | return usedId;
27 | };
28 |
29 |
30 | var remove = function(index) {
31 | delete listeners[index];
32 | }
33 |
34 |
35 | $window.addEventListener(eventName, callback);
36 |
37 | return {
38 | add: add,
39 | remove: remove
40 | };
41 | };
42 |
43 |
44 | var mouseMoveHandler = mouseEventCallbackHandlerFactory('mousemove');
45 | var mouseDownHandler = mouseEventCallbackHandlerFactory('mousedown');
46 | var mouseUpHandler = mouseEventCallbackHandlerFactory('mouseup');
47 |
48 | var touchMoveHandler = mouseEventCallbackHandlerFactory('touchmove');
49 | var touchEndHandler = mouseEventCallbackHandlerFactory('touchend');
50 | var touchCancelHandler = mouseEventCallbackHandlerFactory('touchcancel');
51 |
52 |
53 | return {
54 | addMouseMoveListener: mouseMoveHandler.add,
55 | removeMouseMoveListener: mouseMoveHandler.remove,
56 | addMouseDownListener: mouseDownHandler.add,
57 | removeMouseDownListener: mouseDownHandler.remove,
58 | addMouseUpListener: mouseUpHandler.add,
59 | removeMouseUpListener: mouseUpHandler.remove,
60 | addTouchMoveListener: touchMoveHandler.add,
61 | removeTouchMoveListener: touchMoveHandler.remove,
62 | addTouchEndListener: touchEndHandler.add,
63 | removeTouchEndListener: touchEndHandler.remove,
64 | addTouchCancelListener: touchCancelHandler.add,
65 | removeTouchCancelListener: touchCancelHandler.remove,
66 | };
67 | });
68 | /* @license-end */
69 |
70 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
71 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | var path = require('path');
5 | var webpack = require('webpack');
6 | var ngAnnotatePlugin = require('ng-annotate-webpack-plugin');
7 | var htmlWebpackPlugin = require('html-webpack-plugin');
8 | var CordovaPlugin = require('webpack-cordova-plugin');
9 | var autoprefixer = require('autoprefixer');
10 | var precss = require('precss');
11 |
12 | var useCordova = (function() {
13 | var tmp = parseInt(process.env.BUILD_CORDOVA);
14 | return isNaN(tmp) ? false : tmp !== 0; // fsck JS
15 | })();
16 |
17 |
18 | module.exports = {
19 | resolve: {
20 | root: [
21 | path.join(__dirname, 'node_modules'),
22 | path.join(__dirname, "bower_components"),
23 | ]
24 | },
25 | plugins: [
26 | new webpack.ExtendedAPIPlugin(),
27 | new webpack.ResolverPlugin(
28 | new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
29 | ),
30 | new ngAnnotatePlugin({
31 | add: true
32 | }),
33 | new htmlWebpackPlugin({
34 | template: 'public/lovecall.html',
35 | hash: true,
36 | inject: 'body',
37 | minify: {
38 | removeComments: true,
39 | collapseWhitespace: true,
40 | caseSensitive: true,
41 | },
42 | }),
43 | ],
44 |
45 | devServer: {
46 | contentBase: './build',
47 | },
48 |
49 | entry: "./src/js/main.js",
50 | output: {
51 | path: path.join(__dirname, 'build'),
52 | filename: "assets/bundle.js",
53 | },
54 | module: {
55 | loaders: [
56 | { test: /\.css$/, loader: "style!css!postcss" },
57 | { test: /\.tmpl\.html$/, loader: "ng-cache?-conservativeCollapse&-preserveLineBreaks" },
58 | { test: /\.(jpg|png|woff|woff2|eot|ttf|svg|opus)$/, loader: 'url-loader?limit=100000' },
59 | ]
60 | },
61 | postcss: function() {
62 | return [autoprefixer, precss];
63 | }
64 | };
65 |
66 |
67 | if (useCordova) {
68 | module.exports.plugins.push(
69 | new CordovaPlugin({
70 | config: 'config.xml',
71 | src: 'index.html',
72 | platform: 'android',
73 | version: true,
74 | })
75 | );
76 | }
77 |
78 | /* @license-end */
79 |
80 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
81 |
--------------------------------------------------------------------------------
/src/js/ui/frame.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 |
6 |
7 | var mod = angular.module('lovecall/ui/frame', []);
8 |
9 | mod.factory('FrameManager', function($rootScope, $window, $log) {
10 | // states
11 | var frameCallbacks = [];
12 | var playbackPosMeasure = 0;
13 | var playbackPosStep = 0;
14 |
15 | $log = $log.getInstance('FrameManager');
16 |
17 |
18 | var requestAnimFrame = (function(window) {
19 | return window.requestAnimationFrame ||
20 | window.webkitRequestAnimationFrame ||
21 | window.mozRequestAnimationFrame ||
22 | window.oRequestAnimationFrame ||
23 | window.msRequestAnimationFrame ||
24 | function(/* function */ callback, /* DOMElement */ element) {
25 | window.setTimeout(callback, 1000 / 60);
26 | };
27 | })($window);
28 |
29 |
30 | var frameCallback = function(ts) {
31 | requestAnimFrame(frameCallback);
32 |
33 | var i = 0;
34 | for (; i < frameCallbacks.length; i++) {
35 | frameCallbacks[i](ts);
36 | }
37 | };
38 |
39 |
40 | var addFrameCallback = function(callback) {
41 | frameCallbacks.push(callback);
42 | };
43 |
44 |
45 | var removeFrameCallback = function(callback) {
46 | var i = 0;
47 | var found = false;
48 | for (i = 0; i < frameCallbacks.length; i++) {
49 | if (frameCallbacks[i] == callback) {
50 | found = true;
51 | break;
52 | }
53 | }
54 |
55 | if (found) {
56 | frameCallbacks.splice(i, 1);
57 | }
58 | };
59 |
60 |
61 | var startFrameLoop = function() {
62 | $log.info('Starting frame loop with rAF impl', requestAnimFrame);
63 | requestAnimFrame(frameCallback);
64 | };
65 |
66 |
67 | var tickCallback = function(beat) {
68 | playbackPosMeasure = beat.m;
69 | playbackPosStep = beat.s;
70 | }
71 |
72 |
73 | var getMeasure = function() {
74 | return playbackPosMeasure;
75 | };
76 |
77 |
78 | var getStep = function() {
79 | return playbackPosStep;
80 | };
81 |
82 |
83 | return {
84 | 'addFrameCallback': addFrameCallback,
85 | 'removeFrameCallback': removeFrameCallback,
86 | 'startFrameLoop': startFrameLoop,
87 | 'tickCallback': tickCallback,
88 | 'getMeasure': getMeasure,
89 | 'getStep': getStep,
90 | };
91 | });
92 | /* @license-end */
93 |
94 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
95 |
--------------------------------------------------------------------------------
/src/js/ui/metadata.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 | require('../provider/choreography');
6 | require('../provider/font-selector');
7 |
8 |
9 | var mod = angular.module('lovecall/ui/metadata', [
10 | 'lovecall/provider/choreography',
11 | 'lovecall/provider/font-selector',
12 | ]);
13 |
14 | mod.controller('MetadataController', function($scope, $window, $log, Choreography, FontSelector) {
15 | $log = $log.getInstance('MetadataController');
16 |
17 | $scope.title = '';
18 | $scope.artist = '';
19 | $scope.album = '';
20 | $scope.lang = '';
21 | $scope.fontFamily = 'sans-serif';
22 | $scope.songImage = 'none';
23 |
24 | var metadataImg = document.querySelector('.metadata__image');
25 |
26 |
27 | var setCoverArtUrl = function(url) {
28 | $scope.songImage = 'url(' + url + ')';
29 | };
30 |
31 |
32 | var setSongImage = function(imageBlob) {
33 | if (!imageBlob) {
34 | $scope.songImage = 'none';
35 | return;
36 | }
37 |
38 | var url = $window.URL || $window.webkitURL;
39 | setCoverArtUrl(url.createObjectURL(imageBlob));
40 | };
41 |
42 |
43 | $scope.$on('song:remoteCoverArtRequest', function(e, url) {
44 | $log.debug('using remote cover art', url);
45 | setCoverArtUrl(url);
46 | });
47 |
48 |
49 | $scope.$on('audio:unloaded', function(e) {
50 | $scope.title = '';
51 | $scope.artist = '';
52 | $scope.album = '';
53 | setSongImage(null);
54 | });
55 |
56 |
57 | $scope.$on('audio:loaded', function(e) {
58 | var songMetadata = Choreography.getSongMetadata();
59 | var tempo = Choreography.getTempo();
60 |
61 | $scope.title = songMetadata.ti;
62 | $scope.artist = songMetadata.ar;
63 | $scope.album = songMetadata.al;
64 |
65 | var lang = Choreography.getLanguage();
66 | $scope.lang = lang;
67 | $scope.fontFamily = FontSelector.fontFamilyForLanguage(lang);
68 |
69 | //TODO: change BPM
70 | var rotateDuration = (tempo.stepToTime(20, 0) - tempo.stepToTime(0, 0)) / 1000;
71 | metadataImg.style.animationDuration = rotateDuration + 's';
72 | });
73 |
74 | $scope.$on('audio:resume', function(e) {
75 | metadataImg.style.animationPlayState = 'running';
76 | });
77 |
78 | $scope.$on('audio:pause', function(e) {
79 | metadataImg.style.animationPlayState = 'paused';
80 | });
81 |
82 |
83 | $scope.$on('song:imageLoaded', function(e, imageBlob) {
84 | setSongImage(imageBlob);
85 | });
86 |
87 |
88 | $log.debug('$scope', $scope);
89 | });
90 | /* @license-end */
91 |
--------------------------------------------------------------------------------
/src/js/ui/navigation.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 | require('angular-material');
6 |
7 | require('../provider/song');
8 |
9 | require('../../templates/song-selector.tmpl.html');
10 | require('../../templates/about.tmpl.html');
11 | require('../../templates/config.tmpl.html');
12 |
13 |
14 | var mod = angular.module('lovecall/ui/navigation', [
15 | 'ngMaterial',
16 | 'lovecall/provider/song',
17 | 'lovecall/provider/choreography'
18 | ]);
19 |
20 | mod.controller('NavigationController', function($scope, $mdSidenav, $mdMedia, $mdDialog, $log, Choreography, Song) {
21 | $log = $log.getInstance('NavigationController');
22 |
23 | $scope.showSide = function() {
24 | $mdSidenav('sidenav').open();
25 | };
26 |
27 | $scope.closeSide = function() {
28 | $mdSidenav('sidenav').close();
29 | };
30 |
31 | $scope.$mdMedia = $mdMedia;
32 |
33 | $scope.showSongSelector = function(ev) {
34 | var useFullScreen = ($mdMedia('sm') || $mdMedia('xs'));
35 | $mdDialog.show({
36 | controller: 'SongSelectorController',
37 | templateUrl: 'song-selector.tmpl.html',
38 | parent: angular.element(document.body),
39 | targetEvent: ev,
40 | clickOutsideToClose: true,
41 | fullscreen: useFullScreen
42 | }).then(function(answer) {
43 | $log.debug('selected song index', answer);
44 |
45 | var basename = Choreography.getSongRemoteBasenameByIndex(answer);
46 |
47 | // load song via Ajax
48 | Song.load(answer, basename);
49 | }, function() {
50 | $log.debug('cancelled song select');
51 | });
52 | };
53 |
54 | $scope.showAboutDialog = function(ev) {
55 | $mdDialog.show({
56 | controller: 'AboutDialogController',
57 | templateUrl: 'about.tmpl.html',
58 | parent: angular.element(document.body),
59 | targetEvent: ev,
60 | clickOutsideToClose: true
61 | }).then(function(){}, function(){});
62 | }
63 |
64 | $scope.showConfigDialog = function(ev) {
65 | var useFullScreen = ($mdMedia('sm') || $mdMedia('xs'));
66 | $mdDialog.show({
67 | controller: 'ConfigDialogController',
68 | templateUrl: 'config.tmpl.html',
69 | parent: angular.element(document.body),
70 | targetEvent: ev,
71 | fullscreen: useFullScreen,
72 | clickOutsideToClose: true
73 | }).then(function() {}, function(){});
74 | }
75 |
76 | $scope.canGetApp = function() {
77 | var userAgent = window.navigator.userAgent;
78 | var re = /Android (\d+(?:\.\d+)+);/;
79 |
80 | var version = re.exec(userAgent);
81 | if (window.cordova === undefined && version && version[1] > '4.4') {
82 | return true;
83 | }
84 |
85 | return false;
86 | }
87 |
88 | // close navigation drawer when song loading ends
89 | $scope.$on('song:hideLoadingDialog', function(e, errored) {
90 | $scope.closeSide();
91 | });
92 | });
93 |
--------------------------------------------------------------------------------
/src/images/hi.svg:
--------------------------------------------------------------------------------
1 |
2 |
19 |
--------------------------------------------------------------------------------
/src/js/conf.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 | require('angular');
5 | require('angular-local-storage');
6 |
7 | var siteConf = require('../../lovecall.config');
8 |
9 |
10 | var mod = angular.module('lovecall/conf', [
11 | 'LocalStorageModule',
12 | ]);
13 |
14 | mod.factory('LCConfig', function($rootScope, localStorageService) {
15 | var VERSION = '20160112.2';
16 | var HASH = __webpack_hash__;
17 |
18 |
19 | var getAudioBufferSizeOrder = function() {
20 | var storedSizeOrder = parseInt(localStorageService.get('audioBufferSizeOrder'));
21 | if (isNaN(storedSizeOrder) || storedSizeOrder < 8 || storedSizeOrder > 14) {
22 | storedSizeOrder = 12;
23 | doSetAudioBufferSizeOrder(storedSizeOrder, false);
24 | }
25 |
26 | return storedSizeOrder;
27 | };
28 |
29 |
30 | var getAudioBufferSize = function() {
31 | return 1 << getAudioBufferSizeOrder();
32 | };
33 |
34 |
35 | var setAudioBufferSizeOrder = function(order) {
36 | return doSetAudioBufferSizeOrder(order, true);
37 | };
38 |
39 |
40 | var doSetAudioBufferSizeOrder = function(order, fireEvent) {
41 | var newSizeOrder = order < 8 ? 8 : order > 14 ? 14 : order;
42 | localStorageService.set('audioBufferSizeOrder', '' + newSizeOrder);
43 |
44 | if (fireEvent) {
45 | $rootScope.$broadcast('config:audioBufferSizeChanged', 1 << order);
46 | }
47 | };
48 |
49 |
50 | var isRomajiEnabled = function() {
51 | var storedRomajiEnabled = parseInt(localStorageService.get('romajiEnabled'));
52 | if (isNaN(storedRomajiEnabled)) {
53 | storedRomajiEnabled = 0;
54 | doSetRomajiEnabled(false, false);
55 | }
56 |
57 | return !!storedRomajiEnabled;
58 | };
59 |
60 |
61 | var setRomajiEnabled = function(enabled) {
62 | return doSetRomajiEnabled(enabled, true);
63 | };
64 |
65 |
66 | var doSetRomajiEnabled = function(enabled, fireEvent) {
67 | localStorageService.set('romajiEnabled', enabled ? '1' : '0');
68 | fireEvent && $rootScope.$broadcast('config:romajiEnabledChanged', enabled);
69 | };
70 |
71 |
72 | var getGlobalOffsetMs = function() {
73 | // TODO
74 | return 0;
75 | };
76 |
77 |
78 | var getKnownLocalSongs = function() {
79 | // TODO
80 | return [];
81 | };
82 |
83 |
84 | return {
85 | VERSION: VERSION,
86 | HASH: HASH,
87 | REMOTE_MUSIC_PREFIX: siteConf.remoteMusicPrefix,
88 | REMOTE_COVER_ART_PREFIX: siteConf.remoteCoverArtPrefix,
89 | REMOTE_APK_PREFIX: siteConf.remoteApkPrefix,
90 | getAudioBufferSize: getAudioBufferSize,
91 | getAudioBufferSizeOrder: getAudioBufferSizeOrder,
92 | setAudioBufferSizeOrder: setAudioBufferSizeOrder,
93 | isRomajiEnabled: isRomajiEnabled,
94 | setRomajiEnabled: setRomajiEnabled,
95 | getGlobalOffsetMs: getGlobalOffsetMs,
96 | getKnownLocalSongs: getKnownLocalSongs,
97 | };
98 | });
99 | /* @license-end */
100 |
101 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
102 |
--------------------------------------------------------------------------------
/src/js/data/snowhare.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "lovecall": 0,
3 | "metadata": {
4 | "song": {
5 | "title": "Snow halation",
6 | "artist": "μ's",
7 | "album": "Snow halation",
8 | "lang": "ja",
9 | "remoteBasename": "snowhare",
10 | "sources": {
11 | "fallback:": {
12 | "offset": 401
13 | },
14 | "md5:89efa6e06c8a2e310f20cac27ddb1bcc": {
15 | "offset": 2199
16 | },
17 | "md5:c80b88e686eb5044532d74722bb09069": {
18 | "offset": 401
19 | }
20 | },
21 | "timing": [
22 | [0, 173.0, 4, 4, 0]
23 | ]
24 | },
25 | "palette": [
26 | "#eeeeee",
27 | "#ffa500"
28 | ]
29 | },
30 | "form": [
31 | [0, 0, 9, 0, "I"],
32 | [9, 0, 25, 0, "A1"],
33 | [25, 0, 37, 0, "B1"],
34 | [37, 0, 61, 0, "C1"],
35 | [61, 0, 69, 0, "G1"],
36 | [69, 0, 85, 0, "A2"],
37 | [85, 0, 97, 0, "B2"],
38 | [97, 0, 121, 0, "C2"],
39 | [121, 0, 137, 0, "G2"],
40 | [137, 0, 145, 0, "S"],
41 | [137, 0, 161, 0, "C3"],
42 | [161, 0, 167, 0, "G3"],
43 | [167, 0, -1, -1, "O"]
44 | ],
45 | "colors": [
46 | [-2, -2, 137, 0, 0],
47 | [137, 0, -1, -1, 1]
48 | ],
49 | "timeline": [
50 | [1, 0, 0, 16, 0, "上举", 16],
51 | [0, 16, 0, "fufu"],
52 | [0, 16, 8, "fufu"],
53 | [1, 17, 0, 23, 0, "里打"],
54 | [0, 23, 0, "警报"],
55 | [1, 25, 0, 29, 0, "PPPH", "OOOH"],
56 | [1, 29, 0, 35, 0, "里跳", true],
57 | [1, 35, 0, 36, 0, "快挥"],
58 | [1, 36, 0, 37, 0, "前挥", 8],
59 | [1, 36, 0, 36, 8, "跟唱", "な", "Na"],
60 | [1, 36, 8, 37, 0, "跟唱", "ぜ", "ze"],
61 | [1, 37, 0, 43, 14, "里打"],
62 | [1, 44, 0, 45, 0, "前挥", 8],
63 | [1, 43, 14, 44, 4, "跟唱", "Snow"],
64 | [1, 44, 4, 45, 0, "跟唱", "halation"],
65 | [1, 45, 0, 53, 0, "里打"],
66 | [1, 53, 0, 59, 0, "前挥"],
67 | [1, 59, 0, 63, 0, "上举", 32],
68 | [1, 63, 0, 65, 0, "欢呼"],
69 | [1, 65, 0, 68, 0, "里跳", true],
70 | [0, 68, 0, "fufu"],
71 | [0, 68, 8, "fufu"],
72 | [1, 69, 0, 77, 0, "上举", 16],
73 | [1, 77, 0, 83, 0, "里打"],
74 | [0, 83, 0, "警报"],
75 | [1, 85, 0, 89, 0, "PPPH", "OOOH"],
76 | [1, 89, 0, 95, 0, "里跳", true],
77 | [1, 95, 0, 96, 0, "快挥"],
78 | [1, 96, 0, 97, 0, "前挥", 8],
79 | [1, 96, 0, 96, 8, "跟唱", "Fly"],
80 | [1, 96, 8, 97, 0, "跟唱", "high"],
81 | [1, 97, 0, 103, 14, "里打"],
82 | [1, 104, 0, 105, 0, "前挥", 8],
83 | [1, 103, 14, 104, 4, "跟唱", "True"],
84 | [1, 104, 4, 105, 0, "跟唱", "emotion"],
85 | [1, 105, 0, 113, 0, "里打"],
86 | [1, 113, 0, 119, 0, "前挥"],
87 | [1, 119, 0, 137, 0, "上举", 32],
88 | [1, 137, 0, 143, 14, "上举", 16],
89 | [1, 144, 0, 145, 0, "前挥", 8],
90 | [1, 143, 14, 144, 4, "跟唱", "Snow"],
91 | [1, 144, 4, 145, 0, "跟唱", "halation"],
92 | [1, 145, 0, 153, 0, "里跳"],
93 | [1, 153, 0, 159, 0, "前挥"],
94 | [1, 159, 0, 163, 0, "上举", 32],
95 | [1, 163, 0, 174, 0, "里跳", true],
96 | [0, 174, 0, "fufu"],
97 | [0, 174, 8, "fufu"],
98 | [1, 175, 4, 183, 4, "上举", 128]
99 | ]
100 | };
101 |
--------------------------------------------------------------------------------
/src/images/oh.svg:
--------------------------------------------------------------------------------
1 |
2 |
19 |
--------------------------------------------------------------------------------
/src/images/fu.svg:
--------------------------------------------------------------------------------
1 |
2 |
19 |
--------------------------------------------------------------------------------
/src/js/choreography/tempo.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 |
5 | var tempoFactory = function(timingSpec, globalOffsetMs) {
6 | var timingSections = timingSpec.map(function(v) {
7 | // [offset, bpm, timeSignNumer, timeSignDenom, startingMeasure]
8 | // to
9 | // [offsetMs, stepMs, startingMeasure, stepsPerMeasure, stepsPerStep]
10 | // where a "step" is an 1/16 note
11 | var timeSignNumer = v[2];
12 | var timeSignDenom = v[3];
13 |
14 | var beatMs = 60000 / v[1];
15 | var stepsPerStep = 16 / timeSignDenom;
16 | var stepsPerMeasure = timeSignNumer * stepsPerStep;
17 | var stepMs = beatMs / stepsPerStep;
18 |
19 | return [v[0] + globalOffsetMs, stepMs, v[4], stepsPerMeasure, stepsPerStep];
20 | });
21 |
22 | console.log('tempo: timingSections=', timingSections);
23 |
24 | return {
25 | timingSections: timingSections,
26 | timeToStep: function(posMs) {
27 | // find out the timing section to use
28 | var sectionIdx;
29 | var section;
30 | if (posMs < timingSections[0][0]) {
31 | // just use the first timing section for leading
32 | sectionIdx = 0;
33 | section = timingSections[0];
34 | } else {
35 | for (sectionIdx = timingSections.length - 1; sectionIdx >= 0; sectionIdx--) {
36 | section = timingSections[sectionIdx];
37 | if (section[0] <= posMs) {
38 | break;
39 | }
40 | }
41 | }
42 |
43 | var posAfterOffset = posMs - section[0];
44 | var stepMs = section[1];
45 | var startingMeasure = section[2];
46 | var stepsPerMeasure = section[3];
47 |
48 | var totalSteps = (posAfterOffset / stepMs)|0;
49 | var measure = (startingMeasure + totalSteps / stepsPerMeasure)|0;
50 | var step = (totalSteps % stepsPerMeasure)|0;
51 | return {
52 | m: (totalSteps < 0 ? measure - 1 : measure)|0,
53 | s: (step < 0 ? step + stepsPerMeasure : step)|0,
54 | i: sectionIdx
55 | };
56 | },
57 | stepToTime: function(measure, step) {
58 | var sectionIdx = 0;
59 | var section = timingSections[timingSections.length - 1];
60 |
61 | for (; sectionIdx < timingSections.length; sectionIdx++) {
62 | if (measure < timingSections[sectionIdx][2]) {
63 | var prevIdx = sectionIdx - 1;
64 | section = timingSections[prevIdx < 0 ? 0 : prevIdx];
65 | break;
66 | }
67 | }
68 |
69 | var measureInSection = measure - section[2];
70 | var stepsPerMeasure = section[3];
71 | var stepMs = section[1];
72 | return section[0] + stepMs * (stepsPerMeasure * measureInSection + step);
73 | }
74 | };
75 | };
76 |
77 |
78 | var stepAdd = function(one, other, stepsPerMeasure) {
79 | stepsPerMeasure || (stepsPerMeasure = 16);
80 |
81 | var newMeasure = one.m + other.m;
82 | var newStep = one.s + other.s;
83 | newMeasure += Math.floor(newStep / stepsPerMeasure);
84 | return {
85 | m: newMeasure,
86 | s: newStep % stepsPerMeasure
87 | };
88 | };
89 |
90 |
91 | var stepCompare = function(one, other, stepsPerMeasure) {
92 | stepsPerMeasure || (stepsPerMeasure = 16);
93 |
94 | return (one.m * stepsPerMeasure + one.s) - (other.m * stepsPerMeasure + other.s);
95 | };
96 |
97 |
98 | module.exports = {
99 | 'tempoFactory': tempoFactory,
100 | 'stepAdd': stepAdd,
101 | 'stepCompare': stepCompare
102 | };
103 | /* @license-end */
104 |
105 | // vim:set ai et ts=2 sw=2 sts=2 fenc=utf-8:
106 |
--------------------------------------------------------------------------------
/src/js/engine/audio-compat.js:
--------------------------------------------------------------------------------
1 | /* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */
2 | 'use strict';
3 |
4 |
5 | // only support MP3's for the moment
6 | module.exports = function CompatAudioEngineImpl(commonEngineInterface, FrameManager, logProvider) {
7 | logProvider || (logProvider = console);
8 |
9 | // don't bother checking if even