File

src/app/components/app-deck/app-deck.component.ts

Implements

OnInit AfterViewInit OnDestroy

Metadata

selector app-deck
styleUrls app-deck.component.css
templateUrl ./app-deck.component.html

Index

Properties
Methods
Inputs
HostListeners

Constructor

constructor(musicService: MusicLoaderService, playerService: PlayerService, helpService: HelpService, translationService: TranslationService)
Parameters :
Name Type Optional
musicService MusicLoaderService No
playerService PlayerService No
helpService HelpService No
translationService TranslationService No

Inputs

deckNumber

Type : number

HostListeners

window:resize
window:resize()

Methods

addCUE
addCUE()
Returns : void
applyEffect
applyEffect(i: )
Parameters :
Name Optional
i No
Returns : void
changePitch
changePitch()
Returns : void
createLoop
createLoop(loop: )
Parameters :
Name Optional
loop No
Returns : void
moveLoop
moveLoop(step: )
Parameters :
Name Optional
step No
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 :
Name Optional
cue No
Returns : void

Properties

activeLoop
activeLoop: any
Type : any
activeLoopRegion
activeLoopRegion: any
Type : any
actualLoop
actualLoop: number
Type : number
Default value : 2
beats
beats: any
Type : any
bpm
bpm: any
Type : any
cues
cues: []
Type : []
Default value : []
effects
effects:
Default value : [{}, {}, {}, {}, {}, {}] as any
help
help: any
Type : any
incomingLoop
incomingLoop: null
Type : null
Default value : null
lastLoopEnd
lastLoopEnd: any
Type : any
lastLoopStart
lastLoopStart: any
Type : any
locale
locale: string
Type : string
loopChanger
loopChanger: any
Type : any
loops
loops: []
Type : []
Default value : [0.25, 0.5, 1, 2, 4, 8, 16]
pitch
pitch: number
Type : number
Default value : 0
playerService
playerService: PlayerService
Type : PlayerService
rotation
rotation: number
Type : number
Default value : 0
showedLoops
showedLoops: number
Type : number
Default value : 3
song
song: any
Type : any
waveform
waveform: ElementRef
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>

result-matching ""

    No results matching ""