src/app/components/app-deck/app-deck.component.ts
OnInit
AfterViewInit
OnDestroy
selector | app-deck |
styleUrls | app-deck.component.css |
templateUrl | ./app-deck.component.html |
Properties |
Methods |
Inputs |
HostListeners |
constructor(musicService: MusicLoaderService, playerService: PlayerService, helpService: HelpService, translationService: TranslationService)
|
|||||||||||||||
Parameters :
|
deckNumber
|
Type : |
window:resize |
window:resize()
|
addCUE |
addCUE()
|
Returns :
void
|
applyEffect | ||||
applyEffect(i: )
|
||||
Parameters :
Returns :
void
|
changePitch |
changePitch()
|
Returns :
void
|
createLoop | ||||
createLoop(loop: )
|
||||
Parameters :
Returns :
void
|
moveLoop | ||||
moveLoop(step: )
|
||||
Parameters :
Returns :
void
|
playPause |
playPause()
|
Returns :
void
|
resetCUE |
resetCUE()
|
Returns :
void
|
resetDisc |
resetDisc()
|
Returns :
void
|
resetLoop |
resetLoop()
|
Returns :
void
|
resetPitch |
resetPitch()
|
Returns :
void
|
rotate |
rotate()
|
Returns :
void
|
startCUE | ||||
startCUE(cue: )
|
||||
Parameters :
Returns :
void
|
activeLoop |
activeLoop:
|
Type : any
|
activeLoopRegion |
activeLoopRegion:
|
Type : any
|
actualLoop |
actualLoop:
|
Type : number
|
Default value : 2
|
beats |
beats:
|
Type : any
|
bpm |
bpm:
|
Type : any
|
cues |
cues:
|
Type : []
|
Default value : []
|
effects |
effects:
|
Default value : [{}, {}, {}, {}, {}, {}] as any
|
help |
help:
|
Type : any
|
incomingLoop |
incomingLoop:
|
Type : null
|
Default value : null
|
lastLoopEnd |
lastLoopEnd:
|
Type : any
|
lastLoopStart |
lastLoopStart:
|
Type : any
|
locale |
locale:
|
Type : string
|
loopChanger |
loopChanger:
|
Type : any
|
loops |
loops:
|
Type : []
|
Default value : [0.25, 0.5, 1, 2, 4, 8, 16]
|
pitch |
pitch:
|
Type : number
|
Default value : 0
|
playerService |
playerService:
|
Type : PlayerService
|
rotation |
rotation:
|
Type : number
|
Default value : 0
|
showedLoops |
showedLoops:
|
Type : number
|
Default value : 3
|
song |
song:
|
Type : any
|
waveform |
waveform:
|
Type : ElementRef
|
Decorators : ViewChild
|
import { Component, OnInit, Input, AfterViewInit, OnDestroy, ViewChild, ElementRef, HostListener } from '@angular/core';
import { MusicLoaderService } from '../../services/music-loader.service';
import { PlayerService } from '../../services/player.service';
import { Subscription } from 'rxjs';
import * as WaveSurfer from 'wavesurfer.js';
import * as Cursor from 'wavesurfer.js/dist/plugin/wavesurfer.cursor.min.js';
import * as Regions from 'wavesurfer.js/dist/plugin/wavesurfer.regions.min.js';
import { HelpService } from 'src/app/services/help.service';
import { TranslationService } from 'src/app/services/translation.service';
@Component({
selector: 'app-deck',
templateUrl: './app-deck.component.html',
styleUrls: ['./app-deck.component.css']
})
export class AppDeckComponent implements OnInit, AfterViewInit, OnDestroy {
@Input()
deckNumber: number;
@ViewChild('waveform')
waveform: ElementRef;
private musicSubscription: Subscription;
rotation = 0;
private active = false;
song: any;
bpm: any;
beats: any;
effects = [{}, {}, {}, {}, {}, {}] as any;
pitch = 0;
cues = [];
activeLoop: any;
activeLoopRegion: any;
loops = [0.25, 0.5, 1, 2, 4, 8, 16];
actualLoop = 2;
showedLoops = 3;
lastLoopStart: any;
lastLoopEnd: any;
loopChanger: any;
incomingLoop = null;
help: any;
playerService: PlayerService;
locale: string;
constructor(
private musicService: MusicLoaderService,
playerService: PlayerService,
helpService: HelpService,
translationService: TranslationService
) {
this.playerService = playerService;
helpService.help$.subscribe(help => {
this.help = help;
});
this.locale = translationService.getActualLang();
translationService.getTranslation().onLangChange.subscribe(value => {
this.locale = value.lang;
});
}
ngOnInit() {
setInterval(() => {
this.rotate();
}, 50);
this.effects = this.playerService.effects[this.deckNumber].filter(_ => true);
this.playerService.effects$[this.deckNumber].subscribe(effects => {
const eff = effects as any;
this.effects = eff.filter(_ => true);
});
}
ngAfterViewInit(): void {
setTimeout(() => {
const height = this.waveform.nativeElement.offsetHeight;
requestAnimationFrame(() => {
this.playerService.save(
this.deckNumber,
WaveSurfer.create({
container: '#' + this.waveform.nativeElement.id,
waveColor: 'red',
progressColor: 'purple',
height: height,
responsive: 0,
plugins: [
Cursor.create({
showTime: true,
opacity: 1,
customShowTimeStyle: {
'background-color': '#000',
color: '#fff',
padding: '2px',
height: height,
'font-size': '10px'
}
}),
Regions.create({
regions: []
})
]
})
);
this.playerService.on(this.deckNumber, 'finish', () => {
this.resetDisc();
});
this.playerService.on(this.deckNumber, 'ready', () => {
this.resetDisc();
this.resetCUE();
this.resetPitch();
});
});
this.musicSubscription = this.musicService.decksongs$[this.deckNumber].subscribe(a => {
const data = a as any;
this.song = data.song as File;
this.bpm = data.bpm;
this.beats = data.beats ? data.beats.reverse() : null;
});
// Necessary delay for testing
}, 100);
}
@HostListener('window:resize')
onResize() {
const height = this.waveform.nativeElement.offsetHeight;
this.playerService.adjustHeight(this.deckNumber, height);
}
rotate() {
if (this.active) {
this.rotation = (this.rotation + (10 * (100 + this.pitch)) / 100) % 360;
}
}
resetDisc() {
this.active = false;
this.rotation = 0;
}
ngOnDestroy(): void {
if (this.musicSubscription) {
this.musicSubscription.unsubscribe();
}
}
playPause() {
this.playerService.playPause(this.deckNumber);
this.active = this.playerService.isPlaying(this.deckNumber);
}
applyEffect(i) {
this.playerService.activateEffect(this.deckNumber, i);
}
changePitch() {
const deck = this.deckNumber;
this.playerService.setPitch(deck, (100 + this.pitch) / 100);
}
resetPitch() {
const deck = this.deckNumber;
this.pitch = 0;
this.playerService.setPitch(deck, 1);
}
addCUE() {
if (this.song) {
const cue = {};
cue['percent'] =
(this.playerService.getCurrentTime(this.deckNumber) / this.playerService.getDuration(this.deckNumber)) * 100;
cue['pos'] = this.playerService.getCurrentTime(this.deckNumber) / this.playerService.getDuration(this.deckNumber);
if (this.cues.length === 4) {
this.cues.shift();
}
this.cues.push(cue);
}
}
startCUE(cue) {
this.playerService.playFromPosition(this.deckNumber, this.cues[cue].pos);
}
resetCUE() {
this.cues = [];
}
createLoop(loop) {
if (this.song && this.beats && this.incomingLoop === null) {
const currentTime = this.playerService.getCurrentTime(this.deckNumber);
const index = this.beats.findIndex(e => e <= currentTime);
if (index !== -1) {
if (!this.activeLoop && index) {
this.activeLoop = loop;
if (this.loops.indexOf(loop) - 1 + this.showedLoops > this.loops.length) {
this.actualLoop = this.loops.length - this.showedLoops;
} else if (this.loops.indexOf(loop) - 1 < 0) {
this.actualLoop = 0;
} else {
this.actualLoop = this.loops.indexOf(loop) - 1;
}
const start = this.beats[index];
this.lastLoopStart = start;
const end =
loop >= 1
? this.beats[index - loop]
: this.beats[index] + (this.beats[index - 1] - this.beats[index]) * loop;
this.lastLoopEnd = end;
if (currentTime > end) {
this.playerService.playFromPosition(
this.deckNumber,
start / this.playerService.getDuration(this.deckNumber)
);
}
this.activeLoopRegion = this.playerService.createLoop(this.deckNumber, start, end);
} else if (this.activeLoop !== loop) {
this.resetLoop();
this.incomingLoop = loop;
this.playerService.getInstance(this.deckNumber).on('audioprocess', () => {
if (this.playerService.getCurrentTime(this.deckNumber) >= this.lastLoopEnd) {
this.activeLoop = loop;
this.incomingLoop = null;
if (this.loops.indexOf(loop) - 1 + this.showedLoops > this.loops.length) {
this.actualLoop = this.loops.length - this.showedLoops;
} else if (this.loops.indexOf(loop) - 1 < 0) {
this.actualLoop = 0;
} else {
this.actualLoop = this.loops.indexOf(loop) - 1;
}
const start = this.lastLoopStart;
const indx = this.beats.findIndex(e => e <= this.lastLoopStart);
const end =
loop >= 1
? this.beats[indx - loop]
: this.beats[indx] + (this.beats[indx - 1] - this.beats[indx]) * loop;
this.lastLoopEnd = end;
this.activeLoopRegion = this.playerService.createLoop(this.deckNumber, start, end);
this.playerService.playFromPosition(
this.deckNumber,
start / this.playerService.getDuration(this.deckNumber)
);
this.playerService.getInstance(this.deckNumber).un('audioprocess');
}
});
} else {
this.resetLoop();
}
} else {
this.resetLoop();
}
}
}
resetLoop() {
if (this.activeLoopRegion) {
this.activeLoopRegion.remove();
this.activeLoopRegion = null;
}
this.activeLoop = null;
}
moveLoop(step) {
if (!(this.actualLoop + step + this.showedLoops > this.loops.length || this.actualLoop + step < 0)) {
this.actualLoop += step;
}
}
}
<!--
This file is part of Web Virtual DJ.
Web Virtual DJ is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Web Virtual DJ is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Web Virtual DJ. If not, see <https://www.gnu.org/licenses/>.
-->
<div class="deck-container" [id]="'app_deck_'+deckNumber">
<div class="deck-layout components-container">
<!-- TODO: Make this more visually appealing -->
<div class="sound-wave" #waveform [id]="'deck_'+deckNumber+'_wave'">
<ng-container *ngFor="let cue of cues; let i = index">
<div class="cue-marker" [style.left.%]="cue.percent"></div>
<div class="cue-tag-container" [style.left.%]="cue.percent">
<div class="cue-tag-text">
{{i+1}}
</div>
</div>
</ng-container>
</div>
<div class="dj-deck-container">
<div class="song-name-container">
<div class="song-name" *ngIf="song" [attr.title]="song.name">{{song.name | translate}}</div>
<div class="bpm" *ngIf="bpm">{{bpm*(1+pitch/100)|number:'1.0-3':locale}} bpm</div>
</div>
<div class="dj-deck ">
<div class="plate-loops">
<div class="plate">
<img (dragstart)="$event.preventDefault()" draggable="false" class="vynil"
[ngStyle]="{'transform': 'rotate(' + rotation+ 'deg)'}" src="assets/images/vynil.svg">
</div>
Loops
<div class="button-container button-container--loops" [class.help]="help === 'loop'">
<div class="button button--loops" (click)="moveLoop(-1)"
[id]="'app_deck_'+deckNumber+'_loops_show_smaller'">
<div class="arrow-small-left"></div>
</div>
<div *ngFor="let loopi of loops.slice(actualLoop, actualLoop+showedLoops)" class="button button--loops"
(click)="createLoop(loopi)" [class.button--effects-active]="activeLoop === loopi"
[class.button--effects-ready]="incomingLoop === loopi"
[id]="'app_deck_'+deckNumber+'_loops_activate_'+loopi">{{loopi}}</div>
<div class="button button--loops" (click)="moveLoop(+1)" [id]="'app_deck_'+deckNumber+'_loops_show_bigger'">
<div class="arrow-small-right"></div>
</div>
</div>
</div>
<div class="layout-buttons">
<div class="layout-cue-effects-pitch">
<div class="layout-cue-effects">
{{ 'DECK.CUES' | translate }}
<div class="button-container button-container--cues" [class.help]="help === 'cue'">
<div *ngFor="let i of [0,1,2,3]" class="button button--cues" (click)="startCUE(i)"
[id]="'app_deck_'+deckNumber+'_play_cue_'+(i+1)">{{i+1}}</div>
<div class="button button--cues" (click)="resetCUE()">Reset</div>
</div>
{{ 'DECK.EFFECTS' | translate }}
<div class="button-container--effects" [class.help]="help === 'effects'">
<div class="
button-container button-container--effects--upper-row">
<div *ngFor="let i of [0,1,2]" class=" button
button--effects button--upper-row" (click)="applyEffect(i)"
[class.button--effects-active]="effects[i].active"
[id]="'app_deck_'+deckNumber+'_activate_effect_'+(i+1)">{{i+1}}</div>
</div>
<div class="button-container button-container--effects--lower-row">
<div *ngFor="let i of [3,4,5]" class="button button--effects button--lower-row"
(click)="applyEffect(i)" [class.button--effects-active]="effects[i].active"
[id]="'app_deck_'+deckNumber+'_activate_effect_'+(i+1)">{{i+1}}
</div>
</div>
</div>
</div>
<div class="layout-pitch" [class.help]="help === 'pitch'">
<div class="layout-pitch-data">
{{pitch}}%
</div>
<div class="layout-pitch-slider">
<slider-controller [config]="{id:'pitch_deck_'+deckNumber,
min:-25,
max:25,
vertical:true,
thumb:'sliderthumb-pitch-vertical',
track:'slidertrack-pitch-vertical'}" [(ngModel)]="pitch" (ngModelChange)="changePitch()">
</slider-controller>
</div>
<div class="layout-pitch-reset">
<img (dragstart)="$event.preventDefault()" draggable="false" class="icon" src="assets/images/reset.png"
(click)="resetPitch()">
</div>
</div>
</div>
<div class="
button-container button-container--cue-play" [class.help]="help === 'cue' || help === 'play'">
<div class="button button--cue" (click)="addCUE()" [id]="'app_deck_'+deckNumber+'_cue_button'">CUE</div>
<div class="button button--play" (click)="playPause()" [id]="'app_deck_'+deckNumber+'_play_button'">
<div class="play-pause">
<div class="arrow-right"></div>
<div class="pause-stick"></div>
<div class="pause-space"></div>
<div class="pause-stick"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>