├── TODO.md
├── .gitignore
├── src
├── app
│ ├── fretboard
│ │ ├── beat
│ │ │ ├── beat.component.css
│ │ │ ├── beat.ts
│ │ │ ├── beat.component.html
│ │ │ ├── beat.component.ts
│ │ │ └── beat.service.ts
│ │ ├── note
│ │ │ ├── note.component.css
│ │ │ ├── open
│ │ │ │ ├── note-open.component.ts
│ │ │ │ └── note-open.component.html
│ │ │ ├── note.component.html
│ │ │ ├── guitar
│ │ │ │ ├── note-guitar.component.ts
│ │ │ │ └── note-guitar.component.html
│ │ │ ├── note.ts
│ │ │ ├── note.component.ts
│ │ │ └── ghl
│ │ │ │ ├── note-ghl.component.ts
│ │ │ │ └── note-ghl.component.html
│ │ ├── event
│ │ │ ├── event.component.css
│ │ │ ├── event.ts
│ │ │ ├── event.component.html
│ │ │ ├── event.service.ts
│ │ │ └── event.component.ts
│ │ ├── event-link
│ │ │ ├── event-link.component.css
│ │ │ ├── event-link.component.html
│ │ │ ├── event-link.ts
│ │ │ ├── event-link.component.ts
│ │ │ └── event-link.service.ts
│ │ ├── fretboard
│ │ │ ├── fretboard.component.css
│ │ │ ├── fretboard.ts
│ │ │ ├── fretboard.component.html
│ │ │ └── fretboard.component.ts
│ │ ├── speed
│ │ │ ├── speed-controls.component.css
│ │ │ ├── speed-controls.component.html
│ │ │ ├── speed-controls.component.ts
│ │ │ └── speed.service.ts
│ │ ├── preparer
│ │ │ └── prepared.ts
│ │ └── fretboard.module.ts
│ ├── global
│ │ ├── keybindings
│ │ │ ├── keybindings.component.html
│ │ │ ├── keybindings.component.ts
│ │ │ └── keybindings-actions.service.ts
│ │ ├── modals
│ │ │ ├── modals.component.css
│ │ │ ├── modals.component.html
│ │ │ ├── modals.component.ts
│ │ │ └── keybindings
│ │ │ │ ├── keybindings-modal.component.html
│ │ │ │ ├── keybindings-modal.component.css
│ │ │ │ └── keybindings-modal.component.ts
│ │ ├── actions
│ │ │ ├── actions.component.ts
│ │ │ ├── actions.component.css
│ │ │ └── actions.component.html
│ │ └── global.module.ts
│ ├── controller
│ │ ├── parent-controls
│ │ │ ├── parent-controls.component.css
│ │ │ ├── parent-controls.component.html
│ │ │ └── parent-controls.component.ts
│ │ ├── note-controls
│ │ │ ├── ghl
│ │ │ │ ├── note-controls-ghl.component.css
│ │ │ │ ├── note-controls-ghl.component.ts
│ │ │ │ └── note-controls-ghl.component.html
│ │ │ ├── guitar
│ │ │ │ ├── note-controls-guitar.component.css
│ │ │ │ ├── note-controls-guitar.component.html
│ │ │ │ └── note-controls-guitar.component.ts
│ │ │ ├── note-controls.component.css
│ │ │ └── note-controls.component.ts
│ │ ├── event-controls
│ │ │ ├── event-controls.component.css
│ │ │ └── event-controls.component.html
│ │ ├── bpm
│ │ │ └── bpm.service.ts
│ │ ├── practice-section
│ │ │ └── practice-section.service.ts
│ │ ├── time-signature
│ │ │ └── time-signature.service.ts
│ │ ├── lyric
│ │ │ └── lyric.service.ts
│ │ ├── controller.module.ts
│ │ └── type
│ │ │ └── type.service.ts
│ ├── track
│ │ ├── selector
│ │ │ ├── selector.component.css
│ │ │ ├── selector.component.html
│ │ │ └── selector.component.ts
│ │ ├── converter
│ │ │ ├── converter.component.css
│ │ │ ├── converter.component.html
│ │ │ └── converter.component.ts
│ │ ├── track.module.ts
│ │ ├── track.ts
│ │ ├── track.service.ts
│ │ └── guitar-to-ghl
│ │ │ └── guitar-to-ghl-converter.service.ts
│ ├── file
│ │ ├── download
│ │ │ ├── file-download.component.css
│ │ │ ├── file-download.component.html
│ │ │ └── file-download.component.ts
│ │ ├── select
│ │ │ ├── file-select.component.css
│ │ │ ├── file-select.component.ts
│ │ │ ├── file-select.component.html
│ │ │ └── file-select.component.spec.ts
│ │ ├── file.module.ts
│ │ ├── file.service.ts
│ │ └── file.service.spec.ts
│ ├── time
│ │ ├── scrollbar
│ │ │ ├── scrollbar.component.css
│ │ │ └── scrollbar.component.html
│ │ ├── audio-player-controls
│ │ │ ├── audio-player-controls.component.css
│ │ │ ├── audio-player-controls.component.html
│ │ │ └── audio-player-controls.component.ts
│ │ ├── volume
│ │ │ ├── volume-controls.component.css
│ │ │ ├── volume-controls.component.html
│ │ │ ├── volume-controls.component.ts
│ │ │ └── volume.service.ts
│ │ ├── increment
│ │ │ └── increment.service.ts
│ │ ├── time.module.ts
│ │ ├── audio-player
│ │ │ └── audio-player.service.ts
│ │ ├── duration
│ │ │ └── duration.service.ts
│ │ └── time.service.ts
│ ├── tap-input
│ │ ├── input
│ │ │ ├── tap-input.component.css
│ │ │ ├── tap-input.component.ts
│ │ │ └── tap-input.component.html
│ │ ├── display
│ │ │ ├── tap-display.component.css
│ │ │ ├── tap-display.component.ts
│ │ │ └── tap-display.component.html
│ │ ├── tap-input.module.ts
│ │ └── tap-input.service.ts
│ ├── model
│ │ ├── import-export
│ │ │ ├── file-to-memory
│ │ │ │ ├── file-to-memory.module.ts
│ │ │ │ ├── file-to-memory.service.spec.ts
│ │ │ │ ├── memory-to-file.service.spec.ts
│ │ │ │ ├── memory-to-file.service.ts
│ │ │ │ └── test-file-to-memory.ts
│ │ │ ├── memory-to-model
│ │ │ │ ├── common
│ │ │ │ │ ├── unsupported-track-exporter.service.ts
│ │ │ │ │ ├── unsupported-track-importer.service.ts
│ │ │ │ │ ├── metadata.service.ts
│ │ │ │ │ ├── sync-track-importer.service.ts
│ │ │ │ │ └── sync-track-exporter.service.ts
│ │ │ │ ├── model-to-memory.service.spec.ts
│ │ │ │ ├── memory-to-model.service.spec.ts
│ │ │ │ ├── guitar
│ │ │ │ │ ├── guitar-track-exporter.service.ts
│ │ │ │ │ └── guitar-track-importer.service.ts
│ │ │ │ ├── ghl
│ │ │ │ │ ├── ghl-track-exporter.service.ts
│ │ │ │ │ └── ghl-track-importer.service.ts
│ │ │ │ ├── memory-to-model.module.ts
│ │ │ │ ├── model-to-memory.service.ts
│ │ │ │ ├── memory-to-model.service.ts
│ │ │ │ └── util
│ │ │ │ │ ├── midi-time.service.ts
│ │ │ │ │ └── midi-time.service.spec.ts
│ │ │ ├── model-import-export.module.ts
│ │ │ ├── model-exporter.service.ts
│ │ │ ├── memory.ts
│ │ │ └── model-importer.service.ts
│ │ ├── id-generator
│ │ │ └── id-generator.service.ts
│ │ ├── model.module.ts
│ │ ├── model.service.ts
│ │ └── model.ts
│ ├── app.component.html
│ ├── app.module.ts
│ ├── app.component.ts
│ └── app.component.css
├── tslint.json
├── polyfills.ts
├── vendor.ts
├── index.html
├── main.ts
├── tsconfig.json
└── styles.css
├── assets
└── chart-hero-demo.gif
├── config
├── helpers.js
├── webpack.dev.js
├── karma-test-shim.js
├── karma.conf.js
├── webpack.prod.js
├── webpack.test.js
└── webpack.common.js
├── LICENSE
├── README.md
└── package.json
/TODO.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | .vscode
3 | .idea
4 | node_modules
5 | dist
6 |
--------------------------------------------------------------------------------
/src/app/fretboard/beat/beat.component.css:
--------------------------------------------------------------------------------
1 |
2 | .beat {
3 | outline: none;
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/fretboard/note/note.component.css:
--------------------------------------------------------------------------------
1 |
2 | .note {
3 | outline: none;
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/fretboard/event/event.component.css:
--------------------------------------------------------------------------------
1 |
2 | .event {
3 | outline: none;
4 | }
5 |
--------------------------------------------------------------------------------
/assets/chart-hero-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nb48/chart-hero/HEAD/assets/chart-hero-demo.gif
--------------------------------------------------------------------------------
/src/app/fretboard/event-link/event-link.component.css:
--------------------------------------------------------------------------------
1 |
2 | .event-link {
3 | outline: none;
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/global/keybindings/keybindings.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/app/fretboard/beat/beat.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface Beat {
3 | id: number;
4 | time: number;
5 | y: number;
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/controller/parent-controls/parent-controls.component.css:
--------------------------------------------------------------------------------
1 |
2 | .parent-controls {
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/track/selector/selector.component.css:
--------------------------------------------------------------------------------
1 |
2 | .track-select {
3 | width: 56%;
4 | height: 100%;
5 | margin-left: 42%;
6 | margin-right: 2%;
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/fretboard/fretboard/fretboard.component.css:
--------------------------------------------------------------------------------
1 |
2 | .fretboard {
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | .fretboard > svg {
8 | width: 100%;
9 | height: 100%;
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/file/download/file-download.component.css:
--------------------------------------------------------------------------------
1 |
2 | .file-download {
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | .file-download button {
8 | width: 100%;
9 | margin-bottom: 0.25em;
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/global/modals/modals.component.css:
--------------------------------------------------------------------------------
1 |
2 | .modals {
3 | width: 12em;
4 | height: 100%;
5 | }
6 |
7 | .modals button {
8 | width: 11em;
9 | margin-top: 1em;
10 | margin-left: 1em;
11 | }
12 |
--------------------------------------------------------------------------------
/src/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint-config-airbnb",
3 | "rules": {
4 | "ter-indent": [true, 4],
5 | "strict-boolean-expressions": false,
6 | "no-boolean-literal-compare": false
7 | }
8 | }
--------------------------------------------------------------------------------
/src/app/controller/note-controls/ghl/note-controls-ghl.component.css:
--------------------------------------------------------------------------------
1 |
2 | .note-controls-ghl {
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | .note-controls-ghl > svg {
8 | width: 100%;
9 | height: 100%;
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/track/converter/converter.component.css:
--------------------------------------------------------------------------------
1 |
2 | .converter {
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | .converter button {
8 | min-width: 60%;
9 | margin-right: 0.5em;
10 | float: right;
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/controller/note-controls/guitar/note-controls-guitar.component.css:
--------------------------------------------------------------------------------
1 |
2 | .note-controls-guitar {
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | .note-controls-guitar > svg {
8 | width: 100%;
9 | height: 100%;
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/fretboard/event-link/event-link.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/app/track/converter/converter.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/app/fretboard/event-link/event-link.ts:
--------------------------------------------------------------------------------
1 | import { ModelTrackEventType } from '../../model/model';
2 |
3 | export interface EventLink {
4 | id: number;
5 | type: ModelTrackEventType;
6 | x: number;
7 | y1: number;
8 | y2: number;
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/time/scrollbar/scrollbar.component.css:
--------------------------------------------------------------------------------
1 |
2 | .scrollbar {
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | .scrollbar > svg {
8 | width: 100%;
9 | height: 100%;
10 | }
11 |
12 | .scrollbar > svg rect {
13 | outline: none;
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/fretboard/beat/beat.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | import 'core-js/es6';
2 | import 'core-js/es7/reflect';
3 | require('zone.js/dist/zone');
4 |
5 | if (process.env.ENV === 'production') {
6 | } else {
7 | Error['stackTraceLimit'] = Infinity;
8 | require('zone.js/dist/long-stack-trace-zone');
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/file/download/file-download.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/config/helpers.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var _root = path.resolve(__dirname, '..');
4 |
5 | function root(args) {
6 | args = Array.prototype.slice.call(arguments, 0);
7 | return path.join.apply(path, [_root].concat(args));
8 | }
9 |
10 | exports.root = root;
11 |
--------------------------------------------------------------------------------
/src/app/fretboard/event/event.ts:
--------------------------------------------------------------------------------
1 | import { ModelTrackEventType } from '../../model/model';
2 |
3 | export interface Event {
4 | id: number;
5 | time: number;
6 | type: ModelTrackEventType;
7 | x: number;
8 | y: number;
9 | selected: boolean;
10 | word?: string;
11 | }
12 |
--------------------------------------------------------------------------------
/src/vendor.ts:
--------------------------------------------------------------------------------
1 |
2 | // Angular
3 | import '@angular/common';
4 | import '@angular/core';
5 | import '@angular/platform-browser';
6 | import '@angular/platform-browser-dynamic';
7 |
8 | // RxJS
9 | import 'rxjs';
10 |
11 | // HammerJS
12 | import 'hammerjs';
13 |
14 | // Howler
15 | import 'howler';
16 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Chart Hero
6 |
7 |
8 |
9 |
10 | Loading...
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app/controller/parent-controls/parent-controls.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 | import { enableProdMode } from '@angular/core';
3 |
4 | import { AppModule } from './app/app.module';
5 |
6 | if (process.env.ENV === 'production') {
7 | enableProdMode();
8 | }
9 |
10 | platformBrowserDynamic().bootstrapModule(AppModule);
11 |
--------------------------------------------------------------------------------
/src/app/track/selector/selector.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{track.view}}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/app/fretboard/note/open/note-open.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | import { NoteOpen } from '../note';
4 |
5 | @Component({
6 | selector: '[app-note-open]',
7 | templateUrl: './note-open.component.html',
8 | })
9 | export class NoteOpenComponent {
10 | @Input() note: NoteOpen;
11 | @Input() drawSustain: boolean;
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/fretboard/fretboard/fretboard.ts:
--------------------------------------------------------------------------------
1 | import { Beat } from '../beat/beat';
2 | import { Note } from '../note/note';
3 | import { Event } from '../event/event';
4 | import { EventLink } from '../event-link/event-link';
5 |
6 | export interface Fretboard {
7 | beats: Beat[];
8 | zeroPosition: number;
9 | notes: Note[];
10 | events: Event[];
11 | eventLinks: EventLink[];
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/global/keybindings/keybindings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { KeybindingsService } from './keybindings.service';
4 |
5 | @Component({
6 | selector: 'app-keybindings',
7 | templateUrl: './keybindings.component.html',
8 | })
9 | export class KeybindingsComponent {
10 |
11 | constructor(public service: KeybindingsService) {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "lib": ["es2015", "dom"],
10 | "noImplicitAny": true,
11 | "suppressImplicitAnyIndexErrors": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/global/modals/modals.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/app/tap-input/input/tap-input.component.css:
--------------------------------------------------------------------------------
1 |
2 | .tap-input {
3 | width: 100%;
4 | height: 100%;
5 | display: flex;
6 | }
7 |
8 | .tap-input mat-list {
9 | flex: 1;
10 | }
11 |
12 | .tap-input mat-form-field {
13 | width: 98%;
14 | margin-left: 1%;
15 | margin-right: 1%;
16 | }
17 |
18 | .tap-input button {
19 | margin-top: 1em;
20 | margin-right: calc(1.5% + 0.75em);
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/time/audio-player-controls/audio-player-controls.component.css:
--------------------------------------------------------------------------------
1 |
2 | .audio-player-controls {
3 | margin-right: 2%;
4 | display: flex;
5 | align-items: center;
6 | justify-content: flex-end;
7 | }
8 |
9 | .audio-player-controls mat-form-field {
10 | margin-left: 2%;
11 | margin-top: 0.2em;
12 | width: 7em;
13 | }
14 |
15 | .audio-player-controls input {
16 | color: white;
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/model/import-export/file-to-memory/file-to-memory.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { FileToMemoryService } from './file-to-memory.service';
4 | import { MemoryToFileService } from './memory-to-file.service';
5 |
6 | @NgModule({
7 | providers: [
8 | FileToMemoryService,
9 | MemoryToFileService,
10 | ],
11 | })
12 | export class AppModelFileToMemoryModule {
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/global/actions/actions.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { ActionsService } from '../../model/actions/actions.service';
4 |
5 | @Component({
6 | selector: 'app-actions',
7 | templateUrl: './actions.component.html',
8 | styleUrls: ['./actions.component.css'],
9 | })
10 | export class ActionsComponent {
11 |
12 | constructor(public service: ActionsService) {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/fretboard/speed/speed-controls.component.css:
--------------------------------------------------------------------------------
1 |
2 | .speed-controls {
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | .speed-controls .slider {
8 | display: flex;
9 | justify-content: flex-end;
10 | width: 100%;
11 | }
12 |
13 | .speed-controls .slider label {
14 | order: 1;
15 | padding-top: 0.5em;
16 | margin-right: 0.5em;
17 | }
18 |
19 | .speed-controls .slider mat-slider {
20 | order: 2;
21 | width: 63%;
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/model/import-export/memory-to-model/common/unsupported-track-exporter.service.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter, Injectable } from '@angular/core';
2 |
3 | import { ModelTrack } from '../../../model';
4 | import { MemoryTrack } from '../../memory';
5 |
6 | @Injectable()
7 | export class UnsupportedTrackExporterService {
8 |
9 | export(track: ModelTrack): MemoryTrack[] {
10 | return track.unsupported.length > 0 ? track.unsupported : null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/time/volume/volume-controls.component.css:
--------------------------------------------------------------------------------
1 |
2 | .volume-controls {
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | .volume-controls .slider {
8 | display: flex;
9 | justify-content: flex-end;
10 | width: 100%;
11 | }
12 |
13 | .volume-controls .slider label {
14 | order: 1;
15 | padding-top: 0.5em;
16 | margin-right: 0.5em;
17 | }
18 |
19 | .volume-controls .slider mat-slider {
20 | order: 2;
21 | width: 63%;
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/model/import-export/memory-to-model/common/unsupported-track-importer.service.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter, Injectable } from '@angular/core';
2 |
3 | import { ModelTrack } from '../../../model';
4 | import { MemoryTrack } from '../../memory';
5 |
6 | @Injectable()
7 | export class UnsupportedTrackImporterService {
8 |
9 | import(track: MemoryTrack[]): ModelTrack {
10 | return {
11 | events: [],
12 | unsupported: track ? track : [],
13 | };
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/fretboard/speed/speed-controls.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/app/file/select/file-select.component.css:
--------------------------------------------------------------------------------
1 |
2 | .container {
3 | width: 100%;
4 | display: flex;
5 | }
6 |
7 | .file-select {
8 | order: 1;
9 | flex: 1;
10 | width: 80%;
11 | }
12 |
13 | .file-select .audio-input button,
14 | .file-select .chart-input button {
15 | width: 11em;
16 | }
17 |
18 | .file-select .audio-input label,
19 | .file-select .chart-input label {
20 | margin-left: 1em;
21 | }
22 |
23 | .reset {
24 | order: 2;
25 | margin-top: 2em;
26 | margin-right: 1em;
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/time/volume/volume-controls.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/app/fretboard/speed/speed-controls.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { SpeedService } from './speed.service';
4 |
5 | @Component({
6 | selector: 'app-speed-controls',
7 | templateUrl: './speed-controls.component.html',
8 | styleUrls: ['./speed-controls.component.css'],
9 | })
10 | export class SpeedControlsComponent {
11 |
12 | constructor(public service: SpeedService) {
13 | }
14 |
15 | captureEvent(event: any) {
16 | event.stopPropagation();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/time/volume/volume-controls.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { VolumeService } from './volume.service';
4 |
5 | @Component({
6 | selector: 'app-volume-controls',
7 | templateUrl: './volume-controls.component.html',
8 | styleUrls: ['./volume-controls.component.css'],
9 | })
10 | export class VolumeControlsComponent {
11 |
12 | constructor(public service: VolumeService) {
13 | }
14 |
15 | captureEvent(event: any) {
16 | event.stopPropagation();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/config/webpack.dev.js:
--------------------------------------------------------------------------------
1 | var webpackMerge = require('webpack-merge');
2 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
3 | var commonConfig = require('./webpack.common');
4 |
5 | module.exports = webpackMerge(commonConfig, {
6 | mode: 'development',
7 | devtool: 'cheap-module-eval-source-map',
8 | output: {
9 | publicPath: '/',
10 | filename: '[name].js',
11 | chunkFilename: '[id].chunk.js'
12 | },
13 | plugins: [
14 | new ExtractTextPlugin('[name].css')
15 | ],
16 | devServer: {
17 | historyApiFallback: true
18 | }
19 | });
20 |
--------------------------------------------------------------------------------
/src/app/model/id-generator/id-generator.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | const increment = 10;
4 |
5 | @Injectable()
6 | export class IdGeneratorService {
7 |
8 | nextId: number;
9 |
10 | constructor() {
11 | this.reset();
12 | }
13 |
14 | reset(): void {
15 | this.nextId = increment;
16 | }
17 |
18 | id(): number {
19 | const newId = this.nextId;
20 | this.nextId += increment;
21 | return newId;
22 | }
23 |
24 | catchUp(maxId: number): void {
25 | this.nextId = maxId + increment;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/fretboard/note/note.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/app/model/model.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { ActionsService } from './actions/actions.service';
4 | import { IdGeneratorService } from './id-generator/id-generator.service';
5 | import { AppModelImportExportModule } from './import-export/model-import-export.module';
6 | import { ModelService } from './model.service';
7 |
8 | @NgModule({
9 | imports: [
10 | AppModelImportExportModule,
11 | ],
12 | providers: [
13 | ActionsService,
14 | IdGeneratorService,
15 | ModelService,
16 | ],
17 | })
18 | export class AppModelModule {
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/global/modals/modals.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { MatDialog } from '@angular/material';
3 |
4 | import { KeybindingsModalComponent } from './keybindings/keybindings-modal.component';
5 |
6 | @Component({
7 | selector: 'app-modals',
8 | templateUrl: './modals.component.html',
9 | styleUrls: ['./modals.component.css'],
10 | })
11 | export class ModalsComponent {
12 |
13 | constructor(private dialog: MatDialog) {
14 | }
15 |
16 | keybindings(): void {
17 | this.dialog.open(KeybindingsModalComponent);
18 | }
19 |
20 | metadata(): void {
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/fretboard/event/event.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/app/tap-input/input/tap-input.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { TapInputService } from '../tap-input.service';
4 |
5 | @Component({
6 | selector: 'app-tap-input',
7 | templateUrl: './tap-input.component.html',
8 | styleUrls: ['./tap-input.component.css'],
9 | })
10 | export class TapInputComponent {
11 |
12 | text: string;
13 |
14 | constructor(private service: TapInputService) {
15 | this.clearInput();
16 | }
17 |
18 | clearInput(): void {
19 | this.text = '';
20 | }
21 |
22 | keyDown(e: Event): void {
23 | e.stopPropagation();
24 | this.service.addTime();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/tap-input/input/tap-input.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app/model/import-export/model-import-export.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { AppModelFileToMemoryModule } from './file-to-memory/file-to-memory.module';
4 | import { AppModelMemoryToModelModule } from './memory-to-model/memory-to-model.module';
5 | import { ModelExporterService } from './model-exporter.service';
6 | import { ModelImporterService } from './model-importer.service';
7 |
8 | @NgModule({
9 | imports: [
10 | AppModelFileToMemoryModule,
11 | AppModelMemoryToModelModule,
12 | ],
13 | providers: [
14 | ModelExporterService,
15 | ModelImporterService,
16 | ],
17 | })
18 | export class AppModelImportExportModule {
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/file/select/file-select.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { ModelImporterService } from '../../model/import-export/model-importer.service';
4 | import { FileService } from '../file.service';
5 |
6 | @Component({
7 | selector: 'app-file-select',
8 | templateUrl: './file-select.component.html',
9 | styleUrls: ['./file-select.component.css'],
10 | })
11 | export class FileSelectComponent {
12 |
13 | constructor(
14 | private modelImporter: ModelImporterService,
15 | public service: FileService,
16 | ) {
17 | }
18 |
19 | reset() {
20 | this.modelImporter.import('');
21 | this.service.loadChartFileName('');
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/model/model.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable, ReplaySubject } from 'rxjs';
3 |
4 | import { Model } from './model';
5 |
6 | @Injectable()
7 | export class ModelService {
8 |
9 | private currentModelSubject: ReplaySubject;
10 | private currentModel: Model;
11 |
12 | constructor() {
13 | this.currentModelSubject = new ReplaySubject();
14 | }
15 |
16 | set model(model: Model) {
17 | this.currentModel = model;
18 | this.currentModelSubject.next(model);
19 | }
20 |
21 | get model(): Model {
22 | return this.currentModel;
23 | }
24 |
25 | get models(): Observable {
26 | return this.currentModelSubject.asObservable();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/config/karma-test-shim.js:
--------------------------------------------------------------------------------
1 | Error.stackTraceLimit = Infinity;
2 |
3 | require('core-js/es6');
4 | require('core-js/es7/reflect');
5 |
6 | require('zone.js/dist/zone');
7 | require('zone.js/dist/long-stack-trace-zone');
8 | require('zone.js/dist/proxy');
9 | require('zone.js/dist/sync-test');
10 | require('zone.js/dist/jasmine-patch');
11 | require('zone.js/dist/async-test');
12 | require('zone.js/dist/fake-async-test');
13 |
14 | require('hammerjs');
15 |
16 | var appContext = require.context('../src', true, /\.spec\.ts/);
17 |
18 | appContext.keys().forEach(appContext);
19 |
20 | var testing = require('@angular/core/testing');
21 | var browser = require('@angular/platform-browser-dynamic/testing');
22 |
23 | testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting());
24 |
--------------------------------------------------------------------------------
/src/app/model/import-export/model-exporter.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { ModelService } from '../model.service';
4 | import { MemoryToFileService } from './file-to-memory/memory-to-file.service';
5 | import { ModelToMemoryService } from './memory-to-model/model-to-memory.service';
6 |
7 | @Injectable()
8 | export class ModelExporterService {
9 |
10 | constructor(
11 | private model: ModelService,
12 | private memoryToFile: MemoryToFileService,
13 | private modelToMemory: ModelToMemoryService,
14 | ) {
15 | }
16 |
17 | export(): string {
18 | const model = this.model.model;
19 | const memory = this.modelToMemory.export(model);
20 | const file = this.memoryToFile.export(memory);
21 | return file;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/time/volume/volume.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { BehaviorSubject, Observable } from 'rxjs';
3 |
4 | import { AudioPlayerService } from '../audio-player/audio-player.service';
5 |
6 | const defaultVolume = 25;
7 |
8 | @Injectable()
9 | export class VolumeService {
10 |
11 | private volumeSubject: BehaviorSubject;
12 |
13 | constructor(private audioPlayer: AudioPlayerService) {
14 | this.volumeSubject = new BehaviorSubject(undefined);
15 | this.newVolume(defaultVolume);
16 | }
17 |
18 | get volumes(): Observable {
19 | return this.volumeSubject.asObservable();
20 | }
21 |
22 | newVolume(volume: number) {
23 | this.audioPlayer.setVolume(volume / 100);
24 | this.volumeSubject.next(volume);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/track/converter/converter.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { GuitarToGHLConverterService } from '../guitar-to-ghl/guitar-to-ghl-converter.service';
4 |
5 | @Component({
6 | selector: 'app-converter',
7 | templateUrl: './converter.component.html',
8 | styleUrls: ['./converter.component.css'],
9 | })
10 | export class ConverterComponent {
11 |
12 | shouldConvertExpertGuitarToExpertGHL: boolean;
13 |
14 | constructor(private guitarToGHLConverter: GuitarToGHLConverterService) {
15 | this.guitarToGHLConverter.shouldConverts.subscribe((shouldConvert) => {
16 | this.shouldConvertExpertGuitarToExpertGHL = shouldConvert;
17 | });
18 | }
19 |
20 | convertExpertGuitarToExpertGHLGuitar() {
21 | this.guitarToGHLConverter.convertExpert();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/fretboard/note/guitar/note-guitar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | import { NoteGuitar, NoteGuitarColor } from '../note';
4 |
5 | @Component({
6 | selector: '[app-note-guitar]',
7 | templateUrl: './note-guitar.component.html',
8 | })
9 | export class NoteGuitarComponent {
10 | @Input() note: NoteGuitar;
11 | @Input() drawSustain: boolean;
12 |
13 | get color(): string {
14 | switch (this.note.color) {
15 | case NoteGuitarColor.Green:
16 | return 'green';
17 | case NoteGuitarColor.Red:
18 | return 'red';
19 | case NoteGuitarColor.Yellow:
20 | return 'yellow';
21 | case NoteGuitarColor.Blue:
22 | return 'blue';
23 | case NoteGuitarColor.Orange:
24 | return 'orange';
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/fretboard/event-link/event-link.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | import { ModelTrackEventType } from '../../model/model';
4 | import { EventLink } from './event-link';
5 |
6 | @Component({
7 | selector: '[app-event-link]',
8 | templateUrl: './event-link.component.html',
9 | styleUrls: ['./event-link.component.css'],
10 | })
11 | export class EventLinkComponent {
12 | @Input() eventLink: EventLink;
13 |
14 | get color(): string {
15 | switch (this.eventLink.type) {
16 | case ModelTrackEventType.SoloToggle:
17 | return 'blue';
18 | case ModelTrackEventType.StarPowerToggle:
19 | return 'orange';
20 | case ModelTrackEventType.LyricToggle:
21 | return 'black';
22 | }
23 | throw new Error('Unsupported event link passed to event link component');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/model/import-export/file-to-memory/file-to-memory.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AppModelFileToMemoryModule } from './file-to-memory.module';
4 | import { FileToMemoryService } from './file-to-memory.service';
5 | import { TEST_FILE, TEST_MEMORY } from './test-file-to-memory';
6 |
7 | describe('Service: FileToMemoryService', () => {
8 |
9 | let service: FileToMemoryService;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [
14 | AppModelFileToMemoryModule,
15 | ],
16 | });
17 | service = TestBed.get(FileToMemoryService);
18 | });
19 |
20 | it('FileToMemoryService should transform file to memory correctly', () => {
21 | const memory = service.import(TEST_FILE);
22 | expect(memory).toEqual(TEST_MEMORY);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/app/model/import-export/file-to-memory/memory-to-file.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AppModelFileToMemoryModule } from './file-to-memory.module';
4 | import { MemoryToFileService } from './memory-to-file.service';
5 | import { TEST_FILE, TEST_MEMORY } from './test-file-to-memory';
6 |
7 | describe('Service: MemoryToFileService', () => {
8 |
9 | let service: MemoryToFileService;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [
14 | AppModelFileToMemoryModule,
15 | ],
16 | });
17 | service = TestBed.get(MemoryToFileService);
18 | });
19 |
20 | it('MemoryToFileService should transform memory to file correctly', () => {
21 | const file = service.export(TEST_MEMORY);
22 | expect(file).toEqual(TEST_FILE);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/app/model/import-export/memory-to-model/model-to-memory.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AppModelMemoryToModelModule } from './memory-to-model.module';
4 | import { ModelToMemoryService } from './model-to-memory.service';
5 | import { TEST_MEMORY, TEST_MODEL } from './test-memory-to-model';
6 |
7 | describe('Service: ModelToMemoryService', () => {
8 |
9 | let service: ModelToMemoryService;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [
14 | AppModelMemoryToModelModule,
15 | ],
16 | });
17 | service = TestBed.get(ModelToMemoryService);
18 | });
19 |
20 | it('ModelToMemoryService should transform model into memory correctly', () => {
21 | const memory = service.export(TEST_MODEL);
22 | expect(memory).toEqual(TEST_MEMORY);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | @import '../node_modules/font-awesome/css/font-awesome.css';
2 | @import '~@angular/material/prebuilt-themes/pink-bluegrey.css';
3 |
4 | html, body {
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | .app-tooltip {
10 | font-size: 1.5em;
11 | max-width: 100% !important;
12 | }
13 |
14 | .volume-controls .mat-list-item-content,
15 | .speed-controls .mat-list-item-content,
16 | .tap-input .mat-list-item-content, {
17 | padding-right: 0 !important;
18 | }
19 |
20 | input[type=number]::-webkit-outer-spin-button,
21 | input[type=number]::-webkit-inner-spin-button {
22 | -webkit-appearance: none;
23 | margin: 0;
24 | }
25 |
26 | input[type=number] {
27 | -moz-appearance:textfield;
28 | }
29 |
30 | .no-select {
31 | -webkit-touch-callout: none;
32 | -webkit-user-select: none;
33 | -khtml-user-select: none;
34 | -moz-user-select: none;
35 | -ms-user-select: none;
36 | user-select: none;
37 | }
38 |
--------------------------------------------------------------------------------
/src/app/tap-input/display/tap-display.component.css:
--------------------------------------------------------------------------------
1 |
2 | .tap-display {
3 | width: 94%;
4 | height: 98%;
5 | max-height: 35vh;
6 | margin-left: 4%;
7 | margin-right: 2%;
8 | margin-bottom: 2%;
9 | display: grid;
10 | grid-template-areas: "times actions";
11 | grid-template-columns: 1fr 4em;
12 | }
13 |
14 | .tap-display .times {
15 | grid-area: times;
16 | max-height: 100%;
17 | overflow: auto;
18 | display: flex;
19 | flex-wrap: wrap;
20 | align-content: flex-start;
21 | }
22 |
23 | .tap-display .times .time {
24 | width: 30%;
25 | margin-left: 1.5%;
26 | margin-right: 1.5%;
27 | color: white;
28 | margin-bottom: 2%;
29 | }
30 |
31 | .tap-display .times .selected {
32 | background-color: dimgray;
33 | }
34 |
35 | .tap-display .actions {
36 | grid-area: actions;
37 | }
38 |
39 | .tap-display .actions button {
40 | margin-left: 0.5em;
41 | margin-bottom: 2%;
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/controller/event-controls/event-controls.component.css:
--------------------------------------------------------------------------------
1 |
2 | .event-controls {
3 | width: 100%;
4 | height: 100%;
5 | display: flex;
6 | flex-direction: column;
7 | }
8 |
9 | .event-controls .info {
10 | order: 1;
11 | }
12 |
13 | .event-controls .info label {
14 | width: 35%;
15 | margin-left: 5%;
16 | }
17 |
18 | .event-controls .info input {
19 | width: 60%;
20 | }
21 |
22 | .event-controls .actions {
23 | order: 2;
24 | width: calc(100% - 1em);
25 | height: 10%;
26 | margin-top: 2%;
27 | margin-left: 0.5em;
28 | margin-right: 0.5em;
29 | display: grid;
30 | grid-template-areas: ". . delete-event"
31 | ". . .";
32 | grid-template-columns: 1fr 1fr 1fr;
33 | grid-template-rows: 1fr 1fr;
34 | }
35 |
36 | .event-controls .actions .delete-event {
37 | grid-area: delete-event;
38 | }
39 |
40 | .event-controls .actions button {
41 | margin: 0.5em;
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/fretboard/note/open/note-open.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/app/controller/bpm/bpm.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable, ReplaySubject } from 'rxjs';
3 |
4 | import { ActionsService } from '../../model/actions/actions.service';
5 | import { ModelTrackEvent, ModelTrackBPMChange } from '../../model/model';
6 | import { SelectorService } from '../selector/selector.service';
7 |
8 | @Injectable()
9 | export class BPMService {
10 |
11 | private event: ModelTrackEvent;
12 |
13 | constructor(
14 | private actionsService: ActionsService,
15 | private selectorService: SelectorService,
16 | ) {
17 | this.selectorService.selectedEvents.subscribe((event) => {
18 | this.event = event;
19 | });
20 | }
21 |
22 | updateBPM(bpm: number): void {
23 | const newEvent = JSON.parse(JSON.stringify(this.event));
24 | (newEvent as ModelTrackBPMChange).bpm = bpm;
25 | this.actionsService.syncTrackEventChanged(newEvent);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/fretboard/beat/beat.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | import { showTime } from '../../time/audio-player-controls/audio-player-controls.component';
4 | import { TimeService } from '../../time/time.service';
5 | import { Beat } from './beat';
6 |
7 | @Component({
8 | selector: '[app-beat]',
9 | templateUrl: './beat.component.html',
10 | styleUrls: ['./beat.component.css'],
11 | })
12 | export class BeatComponent {
13 | @Input() beat: Beat;
14 |
15 | constructor(private timeService: TimeService) {
16 | }
17 |
18 | get playing(): boolean {
19 | return this.timeService.playing;
20 | }
21 |
22 | selectAndSnap(event: any): void {
23 | if (!this.timeService.playing) {
24 | this.timeService.time = this.beat.time;
25 | }
26 | event.stopPropagation();
27 | }
28 |
29 | get tooltip(): string {
30 | return `Beat - ${showTime(this.beat.time)}`;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/app/model/import-export/memory.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface Memory {
3 | metadata: MemoryMetadata[];
4 | syncTrack: MemorySyncTrack[];
5 | guitar: {
6 | expert: MemoryTrack[];
7 | hard: MemoryTrack[];
8 | medium: MemoryTrack[];
9 | easy: MemoryTrack[];
10 | };
11 | ghlGuitar: {
12 | expert: MemoryTrack[];
13 | hard: MemoryTrack[];
14 | medium: MemoryTrack[];
15 | easy: MemoryTrack[];
16 | };
17 | events: MemoryTrack[];
18 | vocals: MemoryTrack[];
19 | venue: MemoryTrack[];
20 | }
21 |
22 | export interface MemoryMetadata {
23 | name: string;
24 | value: string;
25 | }
26 |
27 | export interface MemorySyncTrack {
28 | midiTime: number;
29 | type: string;
30 | value?: number;
31 | text?: string;
32 | }
33 |
34 | export interface MemoryTrack {
35 | midiTime: number;
36 | type: string;
37 | note?: number;
38 | length?: number;
39 | text?: string;
40 | }
41 |
--------------------------------------------------------------------------------
/src/app/controller/practice-section/practice-section.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { ActionsService } from '../../model/actions/actions.service';
4 | import { ModelTrackEvent, ModelTrackPracticeSection } from '../../model/model';
5 | import { SelectorService } from '../selector/selector.service';
6 |
7 | @Injectable()
8 | export class PracticeSectionService {
9 |
10 | private event: ModelTrackEvent;
11 |
12 | constructor(
13 | private actionsService: ActionsService,
14 | private selectorService: SelectorService,
15 | ) {
16 | this.selectorService.selectedEvents.subscribe((event) => {
17 | this.event = event;
18 | });
19 | }
20 |
21 | updatePracticeSection(name: string): void {
22 | const newEvent = JSON.parse(JSON.stringify(this.event));
23 | (newEvent as ModelTrackPracticeSection).name = name;
24 | this.actionsService.eventEventChanged(newEvent);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Chart Hero
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/app/controller/time-signature/time-signature.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable, ReplaySubject } from 'rxjs';
3 |
4 | import { ActionsService } from '../../model/actions/actions.service';
5 | import { ModelTrackEvent, ModelTrackTSChange } from '../../model/model';
6 | import { SelectorService } from '../selector/selector.service';
7 |
8 | @Injectable()
9 | export class TimeSignatureService {
10 |
11 | private event: ModelTrackEvent;
12 |
13 | constructor(
14 | private actionsService: ActionsService,
15 | private selectorService: SelectorService,
16 | ) {
17 | this.selectorService.selectedEvents.subscribe((event) => {
18 | this.event = event;
19 | });
20 | }
21 |
22 | updateTimeSignature(ts: number): void {
23 | const newEvent = JSON.parse(JSON.stringify(this.event));
24 | (newEvent as ModelTrackTSChange).ts = ts;
25 | this.actionsService.syncTrackEventChanged(newEvent);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/file/file.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { MatButtonModule, MatListModule, MatTooltipModule } from '@angular/material';
3 | import { BrowserModule } from '@angular/platform-browser';
4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
5 |
6 | import { FileDownloadComponent } from './download/file-download.component';
7 | import { FileSelectComponent } from './select/file-select.component';
8 | import { FileService } from './file.service';
9 |
10 | @NgModule({
11 | imports: [
12 | BrowserModule,
13 | BrowserAnimationsModule,
14 | MatButtonModule,
15 | MatListModule,
16 | MatTooltipModule,
17 | ],
18 | exports: [
19 | FileDownloadComponent,
20 | FileSelectComponent,
21 | ],
22 | declarations: [
23 | FileDownloadComponent,
24 | FileSelectComponent,
25 | ],
26 | providers: [
27 | FileService,
28 | ],
29 | })
30 | export class AppFileModule {
31 | }
32 |
--------------------------------------------------------------------------------
/config/karma.conf.js:
--------------------------------------------------------------------------------
1 | var webpackConfig = require('./webpack.test');
2 |
3 | module.exports = function (config) {
4 | var _config = {
5 | basePath: '',
6 | frameworks: ['jasmine'],
7 | files: [{
8 | pattern: './karma-test-shim.js',
9 | watched: false
10 | }, {
11 | pattern: '../node_modules/@angular/material/prebuilt-themes/pink-bluegrey.css',
12 | watched: false
13 | }],
14 | preprocessors: {
15 | './karma-test-shim.js': ['webpack', 'sourcemap']
16 | },
17 | webpack: webpackConfig,
18 | webpackMiddleware: {
19 | quiet: true
20 | },
21 | webpackServer: {
22 | quiet: true
23 | },
24 | reporters: ['spec'],
25 | port: 9876,
26 | colors: true,
27 | logLevel: config.LOG_WARN,
28 | autoWatch: false,
29 | browsers: ['ChromeHeadless'],
30 | singleRun: true
31 | };
32 | config.set(_config);
33 | };
34 |
--------------------------------------------------------------------------------
/src/app/fretboard/note/guitar/note-guitar.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { AppComponent } from './app.component';
4 |
5 | import { AppControllerModule } from './controller/controller.module';
6 | import { AppFileModule } from './file/file.module';
7 | import { AppFretboardModule } from './fretboard/fretboard.module';
8 | import { AppGlobalModule } from './global/global.module';
9 | import { AppModelModule } from './model/model.module';
10 | import { AppTapInputModule } from './tap-input/tap-input.module';
11 | import { AppTimeModule } from './time/time.module';
12 | import { AppTrackModule } from './track/track.module';
13 |
14 | @NgModule({
15 | imports: [
16 | AppControllerModule,
17 | AppFileModule,
18 | AppFretboardModule,
19 | AppGlobalModule,
20 | AppModelModule,
21 | AppTapInputModule,
22 | AppTimeModule,
23 | AppTrackModule,
24 | ],
25 | declarations: [
26 | AppComponent,
27 | ],
28 | bootstrap: [AppComponent],
29 | })
30 | export class AppModule {
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/global/modals/keybindings/keybindings-modal.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Edit Keybindings
3 |
{{message}}
4 |
7 |
10 |
11 |
12 |
{{keybinding.label}}
13 |
{{keybinding.key}}
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app/model/import-export/memory-to-model/memory-to-model.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { IdGeneratorService } from '../../id-generator/id-generator.service';
4 | import { AppModelMemoryToModelModule } from './memory-to-model.module';
5 | import { MemoryToModelService } from './memory-to-model.service';
6 | import { TEST_MEMORY, TEST_MODEL } from './test-memory-to-model';
7 |
8 | describe('Service: MemoryToModelService', () => {
9 |
10 | let service: MemoryToModelService;
11 |
12 | beforeEach(() => {
13 | TestBed.configureTestingModule({
14 | imports: [
15 | AppModelMemoryToModelModule,
16 | ],
17 | providers: [
18 | IdGeneratorService,
19 | ],
20 | });
21 | service = TestBed.get(MemoryToModelService);
22 | });
23 |
24 | it('MemoryToModelService should transform memory into model correctly', () => {
25 | const model = service.import(TEST_MEMORY);
26 | expect(model).toEqual(TEST_MODEL);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/app/time/scrollbar/scrollbar.component.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/src/app/fretboard/fretboard/fretboard.component.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
--------------------------------------------------------------------------------
/src/app/controller/parent-controls/parent-controls.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { SelectorService } from '../selector/selector.service';
4 |
5 | @Component({
6 | selector: 'app-parent-controls',
7 | templateUrl: './parent-controls.component.html',
8 | styleUrls: ['./parent-controls.component.css'],
9 | })
10 | export class ParentControlsComponent {
11 |
12 | noteSelected: boolean;
13 | eventSelected: boolean;
14 |
15 | constructor(private selectorService: SelectorService) {
16 | this.eventSelected = false;
17 | this.selectorService.selectedEvents.subscribe((event) => {
18 | if (!event) {
19 | this.eventSelected = false;
20 | return;
21 | }
22 | this.eventSelected = true;
23 | });
24 | this.noteSelected = false;
25 | this.selectorService.selectedNotes.subscribe((note) => {
26 | if (!note) {
27 | this.noteSelected = false;
28 | return;
29 | }
30 | this.noteSelected = true;
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017, Nathan Blades
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/app/global/actions/actions.component.css:
--------------------------------------------------------------------------------
1 |
2 | .actions {
3 | width: calc(100% - 0.5em);
4 | height: 100%;
5 | margin-left: 0.5em;
6 | display: grid;
7 | grid-template-areas: "add-note add-bpm-change add-ts-change"
8 | "add-practice-section add-solo-toggle add-star-power-toggle"
9 | "add-phrase add-lyric .";
10 | grid-template-columns: 1fr 1fr 1fr;
11 | grid-template-rows: 1fr 1fr 1fr;
12 | }
13 |
14 | .actions .add-note {
15 | grid-area: add-note;
16 | }
17 |
18 | .actions .add-bpm-change {
19 | grid-area: add-bpm-change;
20 | }
21 |
22 | .actions .add-ts-change {
23 | grid-area: add-ts-change;
24 | }
25 |
26 | .actions .add-practice-section {
27 | grid-area: add-practice-section;
28 | }
29 |
30 | .actions .add-solo-toggle {
31 | grid-area: add-solo-toggle;
32 | }
33 |
34 | .actions .add-star-power-toggle {
35 | grid-area: add-star-power-toggle;
36 | }
37 |
38 | .actions .add-phrase {
39 | grid-area: add-phrase;
40 | }
41 |
42 | .actions .add-lyric {
43 | grid-area: add-lyric;
44 | }
45 |
46 | .actions button {
47 | margin: 0.5em;
48 | }
49 |
--------------------------------------------------------------------------------
/src/app/tap-input/display/tap-display.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { showTime } from '../../time/audio-player-controls/audio-player-controls.component';
4 | import { TapInputService, TapInputTime } from '../tap-input.service';
5 |
6 | @Component({
7 | selector: 'app-tap-display',
8 | templateUrl: './tap-display.component.html',
9 | styleUrls: ['./tap-display.component.css'],
10 | })
11 | export class TapDisplayComponent {
12 |
13 | times: TapInputTime[];
14 |
15 | constructor(private service: TapInputService) {
16 | this.service.times.subscribe((times) => {
17 | this.times = times;
18 | });
19 | }
20 |
21 | captureScroll(event: Event): void {
22 | event.stopPropagation();
23 | }
24 |
25 | showTime(time: TapInputTime): string {
26 | return showTime(time.time);
27 | }
28 |
29 | selectAll(): void {
30 | this.service.selectAll();
31 | }
32 |
33 | deselectAll(): void {
34 | this.service.deselectAll();
35 | }
36 |
37 | createNotes(): void {
38 | this.service.createNotes();
39 | }
40 |
41 | deleteTimes(): void {
42 | this.service.deleteTimes();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/app/track/track.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { FormsModule } from '@angular/forms';
3 | import { MatButtonModule, MatTooltipModule, MatSelectModule } from '@angular/material';
4 | import { BrowserModule } from '@angular/platform-browser';
5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
6 |
7 | import { ConverterComponent } from './converter/converter.component';
8 | import { TrackSelectorComponent } from './selector/selector.component';
9 |
10 | import { GuitarToGHLConverterService } from './guitar-to-ghl/guitar-to-ghl-converter.service';
11 | import { TrackService } from './track.service';
12 |
13 | @NgModule({
14 | imports: [
15 | BrowserModule,
16 | BrowserAnimationsModule,
17 | FormsModule,
18 | MatButtonModule,
19 | MatTooltipModule,
20 | MatSelectModule,
21 | ],
22 | exports: [
23 | ConverterComponent,
24 | TrackSelectorComponent,
25 | ],
26 | declarations: [
27 | ConverterComponent,
28 | TrackSelectorComponent,
29 | ],
30 | providers: [
31 | GuitarToGHLConverterService,
32 | TrackService,
33 | ],
34 | })
35 | export class AppTrackModule {
36 | }
37 |
--------------------------------------------------------------------------------
/src/app/file/download/file-download.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { ModelExporterService } from '../../model/import-export/model-exporter.service';
4 |
5 | @Component({
6 | selector: 'app-file-download',
7 | templateUrl: './file-download.component.html',
8 | styleUrls: ['./file-download.component.css'],
9 | })
10 | export class FileDownloadComponent {
11 |
12 | constructor(public modelExporter: ModelExporterService) {
13 | }
14 |
15 | exportChart() {
16 | const chartString = this.modelExporter.export();
17 | const datetime = new Date()
18 | .toISOString()
19 | .replace(/:/g, '-')
20 | .split('.')[0];
21 | const filename = `notes-${datetime}.chart`;
22 | const chart = new File([chartString], filename, { type: 'text/plain' });
23 | const url = window.URL.createObjectURL(chart);
24 | const link = document.createElement('a');
25 | link.setAttribute('style', 'display: none');
26 | link.setAttribute('href', url);
27 | link.setAttribute('download', filename);
28 | document.body.appendChild(link);
29 | link.click();
30 | window.URL.revokeObjectURL(url);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/app/tap-input/tap-input.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { FormsModule } from '@angular/forms';
3 | import { BrowserModule } from '@angular/platform-browser';
4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
5 | import {
6 | MatButtonModule,
7 | MatFormFieldModule,
8 | MatInputModule,
9 | MatListModule,
10 | MatTooltipModule,
11 | } from '@angular/material';
12 |
13 | import { TapDisplayComponent } from './display/tap-display.component';
14 | import { TapInputComponent } from './input/tap-input.component';
15 |
16 | import { TapInputService } from './tap-input.service';
17 |
18 | @NgModule({
19 | imports: [
20 | BrowserModule,
21 | BrowserAnimationsModule,
22 | FormsModule,
23 | MatButtonModule,
24 | MatFormFieldModule,
25 | MatInputModule,
26 | MatListModule,
27 | MatTooltipModule,
28 | ],
29 | exports: [
30 | TapDisplayComponent,
31 | TapInputComponent,
32 | ],
33 | declarations: [
34 | TapDisplayComponent,
35 | TapInputComponent,
36 | ],
37 | providers: [
38 | TapInputService,
39 | ],
40 | })
41 | export class AppTapInputModule {
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/file/select/file-select.component.html:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/src/app/fretboard/note/note.ts:
--------------------------------------------------------------------------------
1 |
2 | export type Note =
3 | NoteOpen |
4 | NoteGuitar |
5 | NoteGHL;
6 |
7 | export enum NoteType {
8 | Open,
9 | Guitar,
10 | GHL,
11 | }
12 |
13 | export interface NoteOpen {
14 | id: number;
15 | time: number;
16 | type: NoteType.Open;
17 | selected: boolean;
18 | y: number;
19 | sustain: boolean;
20 | endY: number;
21 | hopo: boolean;
22 | tap: boolean;
23 | }
24 |
25 | export interface NoteGuitar {
26 | id: number;
27 | time: number;
28 | type: NoteType.Guitar;
29 | selected: boolean;
30 | x: number;
31 | y: number;
32 | color: NoteGuitarColor;
33 | sustain: boolean;
34 | endY: number;
35 | hopo: boolean;
36 | tap: boolean;
37 | }
38 |
39 | export enum NoteGuitarColor {
40 | Green,
41 | Red,
42 | Yellow,
43 | Blue,
44 | Orange,
45 | }
46 |
47 | export interface NoteGHL {
48 | id: number;
49 | time: number;
50 | type: NoteType.GHL;
51 | selected: boolean;
52 | x: number;
53 | y: number;
54 | color: NoteGHLColor;
55 | sustain: boolean;
56 | endY: number;
57 | hopo: boolean;
58 | tap: boolean;
59 | }
60 |
61 | export enum NoteGHLColor {
62 | Black,
63 | White,
64 | Chord,
65 | }
66 |
--------------------------------------------------------------------------------
/src/app/controller/lyric/lyric.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { ActionsService } from '../../model/actions/actions.service';
4 | import { ModelTrackEvent, ModelTrackLyric } from '../../model/model';
5 | import { SelectorService } from '../selector/selector.service';
6 |
7 | @Injectable()
8 | export class LyricService {
9 |
10 | private event: ModelTrackEvent;
11 |
12 | constructor(
13 | private actionsService: ActionsService,
14 | private selectorService: SelectorService,
15 | ) {
16 | this.selectorService.selectedEvents.subscribe((event) => {
17 | this.event = event;
18 | });
19 | }
20 |
21 | updateWord(word: string): void {
22 | if (!this.event) {
23 | return;
24 | }
25 | const newEvent = JSON.parse(JSON.stringify(this.event)) as ModelTrackLyric;
26 | newEvent.word = word;
27 | this.actionsService.eventEventChanged(newEvent);
28 | }
29 |
30 | flipMultiSyllable(): void {
31 | if (!this.event) {
32 | return;
33 | }
34 | const newEvent = JSON.parse(JSON.stringify(this.event)) as ModelTrackLyric;
35 | newEvent.multiSyllable = !newEvent.multiSyllable;
36 | this.actionsService.eventEventChanged(newEvent);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/model/import-export/memory-to-model/guitar/guitar-track-exporter.service.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter, Injectable } from '@angular/core';
2 |
3 | import { ModelTrack, ModelTrackNoteType } from '../../../model';
4 | import { MemoryTrack } from '../../memory';
5 | import {
6 | GenericTrackExporterService,
7 | NoteExporter,
8 | } from '../generic/generic-track-exporter.service';
9 |
10 | const guitarNoteExporter: NoteExporter = (note: ModelTrackNoteType): number => {
11 | switch (note) {
12 | case ModelTrackNoteType.GuitarGreen:
13 | return 0;
14 | case ModelTrackNoteType.GuitarRed:
15 | return 1;
16 | case ModelTrackNoteType.GuitarYellow:
17 | return 2;
18 | case ModelTrackNoteType.GuitarBlue:
19 | return 3;
20 | case ModelTrackNoteType.GuitarOrange:
21 | return 4;
22 | }
23 | };
24 |
25 | @Injectable()
26 | export class GuitarTrackExporterService {
27 |
28 | constructor(private genericExporter: GenericTrackExporterService) {
29 | }
30 |
31 | export(
32 | track: ModelTrack,
33 | syncTrack: ModelTrack,
34 | resolution: number,
35 | offset: number,
36 | ): MemoryTrack[] {
37 | return this.genericExporter
38 | .export(track, syncTrack, resolution, offset, guitarNoteExporter);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/config/webpack.prod.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var webpackMerge = require('webpack-merge');
3 | var ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
4 | var BaseHrefWebpackPlugin = require('base-href-webpack-plugin').BaseHrefWebpackPlugin;
5 | var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
6 | var commonConfig = require('./webpack.common');
7 | var helpers = require('./helpers');
8 |
9 | const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
10 |
11 | module.exports = webpackMerge(commonConfig, {
12 | mode: 'production',
13 | devtool: 'source-map',
14 | output: {
15 | path: helpers.root('dist'),
16 | publicPath: '/chart-hero/',
17 | filename: '[name].[hash].js',
18 | chunkFilename: '[id].[hash].chunk.js'
19 | },
20 | plugins: [
21 | new webpack.NoEmitOnErrorsPlugin(),
22 | new ExtractTextWebpackPlugin('[name].[hash].css'),
23 | new BaseHrefWebpackPlugin({ baseHref: '/chart-hero/' }),
24 | new webpack.DefinePlugin({
25 | 'process.env': {
26 | 'ENV': JSON.stringify(ENV)
27 | }
28 | })
29 | ],
30 | optimization: {
31 | minimizer: [
32 | new UglifyJsPlugin()
33 | ]
34 | },
35 | performance: {
36 | hints: false
37 | }
38 | });
39 |
--------------------------------------------------------------------------------
/src/app/model/import-export/memory-to-model/ghl/ghl-track-exporter.service.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter, Injectable } from '@angular/core';
2 |
3 | import { ModelTrack, ModelTrackNoteType } from '../../../model';
4 | import { MemoryTrack } from '../../memory';
5 | import {
6 | GenericTrackExporterService,
7 | NoteExporter,
8 | } from '../generic/generic-track-exporter.service';
9 |
10 | const ghlNoteExporter: NoteExporter = (note: ModelTrackNoteType): number => {
11 | switch (note) {
12 | case ModelTrackNoteType.GHLBlack1:
13 | return 3;
14 | case ModelTrackNoteType.GHLBlack2:
15 | return 4;
16 | case ModelTrackNoteType.GHLBlack3:
17 | return 8;
18 | case ModelTrackNoteType.GHLWhite1:
19 | return 0;
20 | case ModelTrackNoteType.GHLWhite2:
21 | return 1;
22 | case ModelTrackNoteType.GHLWhite3:
23 | return 2;
24 | }
25 | };
26 |
27 | @Injectable()
28 | export class GHLTrackExporterService {
29 |
30 | constructor(private genericExporter: GenericTrackExporterService) {
31 | }
32 |
33 | export(
34 | track: ModelTrack,
35 | syncTrack: ModelTrack,
36 | resolution: number,
37 | offset: number,
38 | ): MemoryTrack[] {
39 | return this.genericExporter.export(track, syncTrack, resolution, offset, ghlNoteExporter);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/app/time/audio-player-controls/audio-player-controls.component.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/app/fretboard/fretboard/fretboard.component.ts:
--------------------------------------------------------------------------------
1 | import { SelectorService } from './../../controller/selector/selector.service';
2 | import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
3 |
4 | import { Beat } from '../beat/beat';
5 | import { Event } from '../event/event';
6 | import { EventLink } from '../event-link/event-link';
7 | import { Note } from '../note/note';
8 | import { Fretboard } from './fretboard';
9 |
10 | @Component({
11 | selector: 'app-fretboard',
12 | templateUrl: './fretboard.component.html',
13 | styleUrls: ['./fretboard.component.css'],
14 | changeDetection: ChangeDetectionStrategy.OnPush,
15 | })
16 | export class FretboardComponent {
17 | @Input() fretboard: Fretboard;
18 |
19 | constructor(private selectorService: SelectorService) {
20 | }
21 |
22 | clearSelection(): void {
23 | this.selectorService.clearSelection();
24 | }
25 |
26 | trackBeat(index: number, item: Beat) {
27 | return item.id;
28 | }
29 |
30 | trackNoteSustain(index: number, item: Note) {
31 | return -item.id;
32 | }
33 |
34 | trackNote(index: number, item: Note) {
35 | return item.id;
36 | }
37 |
38 | trackEvent(index: number, item: Event) {
39 | return item.id;
40 | }
41 |
42 | trackEventLink(index: number, item: EventLink) {
43 | return item.id;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/config/webpack.test.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var helpers = require('./helpers');
3 |
4 | module.exports = {
5 | mode: 'development',
6 | devtool: 'inline-source-map',
7 | resolve: {
8 | extensions: ['.ts', '.js']
9 | },
10 | module: {
11 | rules: [{
12 | test: /\.ts$/,
13 | loaders: [{
14 | loader: 'awesome-typescript-loader',
15 | options: {
16 | configFileName: helpers.root('src', 'tsconfig.json'),
17 | silent: true
18 | }
19 | }, {
20 | loader: 'angular2-template-loader'
21 | }]
22 | }, {
23 | test: /\.html$/,
24 | loader: 'html-loader'
25 | }, {
26 | test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
27 | loader: 'null-loader'
28 | }, {
29 | test: /\.css$/,
30 | exclude: helpers.root('src', 'app'),
31 | loader: 'null-loader'
32 | }, {
33 | test: /\.css$/,
34 | include: helpers.root('src', 'app'),
35 | loader: 'raw-loader'
36 | }]
37 | },
38 | plugins: [
39 | new webpack.ContextReplacementPlugin(
40 | /angular(\\|\/)core(\\|\/)@angular/,
41 | helpers.root('src'),
42 | {}
43 | )
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chart Hero 1.1.0
2 |
3 | Chart Hero is a web based chart editing application for Guitar Hero style rhythm games. With Chart Hero, you can create new custom charts in your browser on any desktop operating system.
4 |
5 | Head to https://nb48.github.io/chart-hero/ to check out the application. All you need is an audio file. (.mp3, .ogg, or anything else your browser supports)
6 |
7 | There are some examples here - https://drive.google.com/open?id=1T5JM1XR1tfY1WS6N4kgyKyeFU94zLd5Z
8 |
9 |
10 |
11 | ## Running
12 |
13 | ```
14 | git clone git@github.com:nb48/chart-hero.git
15 | cd chart-hero
16 | yarn install
17 | yarn start
18 | ```
19 |
20 | This starts the application at http://localhost:8080
21 |
22 | ## Testing
23 |
24 | ```
25 | yarn test
26 | ```
27 |
28 | ## Building
29 |
30 | ```
31 | yarn build
32 | ```
33 |
34 | ## Built With
35 |
36 | * [Angular](https://github.com/angular)
37 | * [TypeScript](https://github.com/Microsoft/TypeScript)
38 | * [webpack](https://github.com/webpack)
39 | * [howler.js](https://github.com/goldfire/howler.js)
40 |
41 | ## Styled With
42 |
43 | * [Angular Material](https://material.angular.io/)
44 |
45 | ## License
46 |
47 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
48 |
49 | ## Acknowledgments
50 |
51 | Thanks to the Guitar Hero community for helping to design, test, and improve Chart Hero
52 |
--------------------------------------------------------------------------------
/src/app/fretboard/preparer/prepared.ts:
--------------------------------------------------------------------------------
1 | import { ModelTrackEventType } from '../../model/model';
2 |
3 | export interface Prepared {
4 | beats: PreparedBeat[];
5 | notes: PreparedNote[];
6 | events: PreparedEvent[];
7 | eventLinks: PreparedEventLink[];
8 | }
9 |
10 | export interface PreparedBeat {
11 | id: number;
12 | time: number;
13 | }
14 |
15 | export interface PreparedNote {
16 | id: number;
17 | time: number;
18 | length: number;
19 | hopo: boolean;
20 | tap: boolean;
21 | open: boolean;
22 | guitarLane1: PreparedNoteGuitarColor;
23 | guitarLane2: PreparedNoteGuitarColor;
24 | guitarLane3: PreparedNoteGuitarColor;
25 | guitarLane4: PreparedNoteGuitarColor;
26 | guitarLane5: PreparedNoteGuitarColor;
27 | ghlLane1: PreparedNoteGHLColor;
28 | ghlLane2: PreparedNoteGHLColor;
29 | ghlLane3: PreparedNoteGHLColor;
30 | }
31 |
32 | export enum PreparedNoteGuitarColor {
33 | None,
34 | Green,
35 | Red,
36 | Yellow,
37 | Blue,
38 | Orange,
39 | }
40 |
41 | export enum PreparedNoteGHLColor {
42 | None,
43 | Black,
44 | White,
45 | Chord,
46 | }
47 |
48 | export interface PreparedEvent {
49 | id: number;
50 | time: number;
51 | type: ModelTrackEventType;
52 | word?: string;
53 | }
54 |
55 | export interface PreparedEventLink {
56 | id: number;
57 | startTime: number;
58 | endTime: number;
59 | type: ModelTrackEventType;
60 | }
61 |
--------------------------------------------------------------------------------
/src/app/global/modals/keybindings/keybindings-modal.component.css:
--------------------------------------------------------------------------------
1 |
2 | .keybindings-modal {
3 | width: 100%;
4 | height: 100%;
5 | display: grid;
6 | grid-template-areas: "title reset close"
7 | "message message message"
8 | "keybindings keybindings .";
9 | grid-template-columns: 1fr 3em 3em;
10 | grid-template-rows: 3em 2em 1fr;
11 | }
12 |
13 | .keybindings-modal .title {
14 | grid-area: title;
15 | }
16 |
17 | .keybindings-modal .reset {
18 | grid-area: reset;
19 | margin-left: 1em;
20 | }
21 |
22 | .keybindings-modal .close {
23 | grid-area: close;
24 | margin-left: 1em;
25 | }
26 |
27 | .keybindings-modal .message {
28 | grid-area: message;
29 | margin-left: 1em;
30 | }
31 |
32 | .keybindings-modal .keybindings {
33 | grid-area: keybindings;
34 | margin: 1em;
35 | max-height: 50vh;
36 | overflow: auto;
37 | overflow-x: hidden;
38 | }
39 |
40 | .keybindings-modal .keybindings .keybinding {
41 | width: 38em;
42 | margin-bottom: 1em;
43 | margin-right: 2em;
44 | padding: 1em;
45 | border-width: 2px;
46 | border-color: white;
47 | border-style: solid;
48 | cursor: pointer;
49 | }
50 |
51 | .keybindings-modal .keybindings .action {
52 | display: inline-block;
53 | width: 25em;
54 | margin-right: 1em;
55 | }
56 |
57 | .keybindings-modal .keybindings .key {
58 | display: inline-block;
59 | width: 8em;
60 | }
61 |
--------------------------------------------------------------------------------
/src/app/model/import-export/model-importer.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { SelectorService } from '../../controller/selector/selector.service';
4 | import { TimeService } from '../../time/time.service';
5 | import { TrackService } from '../../track/track.service';
6 | import { IdGeneratorService } from '../id-generator/id-generator.service';
7 | import { ModelService } from '../model.service';
8 | import { FileToMemoryService } from './file-to-memory/file-to-memory.service';
9 | import { MemoryToModelService } from './memory-to-model/memory-to-model.service';
10 |
11 | @Injectable()
12 | export class ModelImporterService {
13 |
14 | constructor(
15 | private selectorService: SelectorService,
16 | private timeService: TimeService,
17 | private trackService: TrackService,
18 | private idGenerator: IdGeneratorService,
19 | private modelService: ModelService,
20 | private fileToMemory: FileToMemoryService,
21 | private memoryToModel: MemoryToModelService,
22 | ) {
23 | this.import('');
24 | }
25 |
26 | import(file: string): void {
27 | this.idGenerator.reset();
28 | const memory = this.fileToMemory.import(file);
29 | const model = this.memoryToModel.import(memory);
30 | this.timeService.time = 0;
31 | this.trackService.defaultTrack(model);
32 | this.modelService.model = model;
33 | this.selectorService.clearSelection();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/app/tap-input/display/tap-display.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{showTime(time)}}
5 |
6 |
7 |
8 |
11 |
14 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/app/track/track.ts:
--------------------------------------------------------------------------------
1 | import { Model, ModelTrack } from '../model/model';
2 |
3 | export enum Track {
4 | GuitarExpert,
5 | GuitarHard,
6 | GuitarMedium,
7 | GuitarEasy,
8 | GHLGuitarExpert,
9 | GHLGuitarHard,
10 | GHLGuitarMedium,
11 | GHLGuitarEasy,
12 | Events,
13 | Vocals,
14 | Venue,
15 | }
16 |
17 | export const getTrack = (cs: Model, track: Track): ModelTrack => {
18 | switch (track) {
19 | case Track.GuitarExpert:
20 | return cs.guitar.expert;
21 | case Track.GuitarHard:
22 | return cs.guitar.hard;
23 | case Track.GuitarMedium:
24 | return cs.guitar.medium;
25 | case Track.GuitarEasy:
26 | return cs.guitar.easy;
27 | case Track.GHLGuitarExpert:
28 | return cs.ghlGuitar.expert;
29 | case Track.GHLGuitarHard:
30 | return cs.ghlGuitar.hard;
31 | case Track.GHLGuitarMedium:
32 | return cs.ghlGuitar.medium;
33 | case Track.GHLGuitarEasy:
34 | return cs.ghlGuitar.easy;
35 | case Track.Events:
36 | return cs.events;
37 | case Track.Vocals:
38 | return cs.vocals;
39 | case Track.Venue:
40 | return cs.venue;
41 | }
42 | };
43 |
44 | export const isGHLTrack = (track: Track): boolean => {
45 | switch (track) {
46 | case Track.GHLGuitarExpert:
47 | case Track.GHLGuitarHard:
48 | case Track.GHLGuitarMedium:
49 | case Track.GHLGuitarEasy:
50 | return true;
51 | default:
52 | return false;
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/src/app/model/import-export/memory-to-model/common/metadata.service.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter, Injectable } from '@angular/core';
2 |
3 | import { Model, ModelMetadata } from '../../../model';
4 | import { MemoryMetadata } from '../../memory';
5 |
6 | @Injectable()
7 | export class MetadataService {
8 |
9 | import(metadata: MemoryMetadata[]): ModelMetadata[] {
10 | if (!metadata) {
11 | return [this.defaultResolution(), this.defaultOffset()];
12 | }
13 | if (!metadata.find(m => m.name === 'Resolution')) {
14 | metadata.push(this.defaultResolution());
15 | }
16 | if (!metadata.find(m => m.name === 'Offset')) {
17 | metadata.push(this.defaultOffset());
18 | }
19 | return metadata as ModelMetadata[];
20 | }
21 |
22 | export(metadata: ModelMetadata[]): MemoryMetadata[] {
23 | return metadata as MemoryMetadata[];
24 | }
25 |
26 | getResolution(metadata: ModelMetadata[]): number {
27 | return parseFloat(metadata.find(m => m.name === 'Resolution').value);
28 | }
29 |
30 | getOffset(metadata: ModelMetadata[]): number {
31 | return parseFloat(metadata.find(m => m.name === 'Offset').value);
32 | }
33 |
34 | private defaultResolution(): ModelMetadata {
35 | return {
36 | name: 'Resolution',
37 | value: '192',
38 | };
39 | }
40 |
41 | private defaultOffset(): ModelMetadata {
42 | return {
43 | name: 'Offset',
44 | value: '0',
45 | };
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/track/track.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable, ReplaySubject } from 'rxjs';
3 |
4 | import { Track } from './track';
5 | import { Model, ModelTrack } from '../model/model';
6 |
7 | @Injectable()
8 | export class TrackService {
9 |
10 | private tracksSubject: ReplaySubject