From 5317daa43e33b67550148bd53220faf20c2fa2de Mon Sep 17 00:00:00 2001 From: Baptiste Lyet <44911483+Babali42@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:38:50 +0100 Subject: [PATCH] Change tempo (#108) * create bpm input component * add style to the bpm input component and adjust sequencer * modify input value with input or buttons * change bpm while playing beat --- .../bpm-input/bpm-input.component.html | 5 ++ .../bpm-input/bpm-input.component.scss | 39 +++++++++++++ .../bpm-input/bpm-input.component.spec.ts | 56 +++++++++++++++++++ .../bpm-input/bpm-input.component.ts | 37 ++++++++++++ .../sequencer/sequencer.component.html | 8 +-- .../sequencer/sequencer.component.scss | 4 ++ .../sequencer/sequencer.component.ts | 18 +++++- .../src/app/services/sound/sound.service.ts | 19 ++++++- 8 files changed, 178 insertions(+), 8 deletions(-) create mode 100644 frontend/src/app/components/bpm-input/bpm-input.component.html create mode 100644 frontend/src/app/components/bpm-input/bpm-input.component.scss create mode 100644 frontend/src/app/components/bpm-input/bpm-input.component.spec.ts create mode 100644 frontend/src/app/components/bpm-input/bpm-input.component.ts diff --git a/frontend/src/app/components/bpm-input/bpm-input.component.html b/frontend/src/app/components/bpm-input/bpm-input.component.html new file mode 100644 index 0000000..5d0024d --- /dev/null +++ b/frontend/src/app/components/bpm-input/bpm-input.component.html @@ -0,0 +1,5 @@ +
+
+ +
+
diff --git a/frontend/src/app/components/bpm-input/bpm-input.component.scss b/frontend/src/app/components/bpm-input/bpm-input.component.scss new file mode 100644 index 0000000..d71e447 --- /dev/null +++ b/frontend/src/app/components/bpm-input/bpm-input.component.scss @@ -0,0 +1,39 @@ +/* From Uiverse.io by Cybercom682 */ +.number-control { + display: flex; + align-items: center; + + .number-left::before, + .number-right::after { + content: attr(data-content); + background-color: var(--backgroundColor);; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--borderColor);; + width: 20px; + color: var(--textColor); + cursor: pointer; + } + + .number-left::before { + content: "-"; + } + + .number-right::after { + content: "+"; + } + + .number-quantity { + padding: 0.25rem; + width: 50px; + height: 12px; + -moz-appearance: textfield; + border: 0; + border-top: 1px solid var(--borderColor); + border-bottom: 1px solid var(--borderColor); + background-color: var(--backgroundColor); + color: var(--textColor); + } +} + diff --git a/frontend/src/app/components/bpm-input/bpm-input.component.spec.ts b/frontend/src/app/components/bpm-input/bpm-input.component.spec.ts new file mode 100644 index 0000000..e5a82e4 --- /dev/null +++ b/frontend/src/app/components/bpm-input/bpm-input.component.spec.ts @@ -0,0 +1,56 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BpmInputComponent } from './bpm-input.component'; +import {By} from "@angular/platform-browser"; + +describe('BpmInputComponent', () => { + let component: BpmInputComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [BpmInputComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BpmInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should increment the bpm in the desired range', () => { + component.maxBpm = 130; + component.bpm = 128; + component.incrementBpm(); + component.incrementBpm(); + component.incrementBpm(); + expect(component.bpm).toEqual(component.maxBpm); + }); + + it('should decrement the bpm in the desired range', () => { + component.minBpm = 126; + component.bpm = 128; + component.decrementBpm(); + component.decrementBpm(); + component.decrementBpm(); + expect(component.bpm).toEqual(component.minBpm); + }); + + it('should update the value', () => { + component.bpm = 128; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const inputElement = fixture.debugElement.query(By.css('.number-quantity')).nativeElement; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + inputElement.value = '120'; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call + inputElement.dispatchEvent(new Event('change')); + fixture.detectChanges(); + + expect(component.bpm).toBe(120); + }); +}); diff --git a/frontend/src/app/components/bpm-input/bpm-input.component.ts b/frontend/src/app/components/bpm-input/bpm-input.component.ts new file mode 100644 index 0000000..c397536 --- /dev/null +++ b/frontend/src/app/components/bpm-input/bpm-input.component.ts @@ -0,0 +1,37 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {SoundService} from "../../services/sound/sound.service"; + +@Component({ + selector: 'app-bpm-input', + standalone: true, + imports: [], + templateUrl: './bpm-input.component.html', + styleUrl: './bpm-input.component.scss' +}) +export class BpmInputComponent { + maxBpm = SoundService.maxBpm; + minBpm = SoundService.minBpm; + @Input() bpm : number = SoundService.minBpm; + @Output() bpmChange = new EventEmitter(); + + incrementBpm() { + this.updateBpm(Math.min(this.bpm+1, this.maxBpm)); + } + + decrementBpm() { + this.updateBpm(Math.max(this.bpm-1, this.minBpm)); + } + + updateNumber(event: Event): void { + const inputElement = event.target as HTMLInputElement; + let value = Number(inputElement.value); + value = Math.min(value, this.maxBpm); + value = Math.max(value, this.minBpm); + this.updateBpm(value); + } + + private updateBpm(value: number): void { + this.bpm = value; + this.bpmChange.emit(this.bpm); + } +} diff --git a/frontend/src/app/components/sequencer/sequencer.component.html b/frontend/src/app/components/sequencer/sequencer.component.html index b868802..e9300c5 100644 --- a/frontend/src/app/components/sequencer/sequencer.component.html +++ b/frontend/src/app/components/sequencer/sequencer.component.html @@ -4,8 +4,7 @@ {{ this.soundService.isPlaying ? "⏸" : "▶" }} -
-
+
Genre : {{ genre.label }} @@ -14,12 +13,11 @@ Beat : {{ beat.label }}
-
+
Tempo : - {{ beat.bpm }} + bpm
-

{{ track.name }}

diff --git a/frontend/src/app/components/sequencer/sequencer.component.scss b/frontend/src/app/components/sequencer/sequencer.component.scss index 368024f..87b8636 100644 --- a/frontend/src/app/components/sequencer/sequencer.component.scss +++ b/frontend/src/app/components/sequencer/sequencer.component.scss @@ -113,6 +113,10 @@ $gap: 4px; font-weight: bold; } } + + div + div { + margin-top: 3px; + } } .play-button-container { diff --git a/frontend/src/app/components/sequencer/sequencer.component.ts b/frontend/src/app/components/sequencer/sequencer.component.ts index fe6df31..89149d6 100644 --- a/frontend/src/app/components/sequencer/sequencer.component.ts +++ b/frontend/src/app/components/sequencer/sequencer.component.ts @@ -4,16 +4,17 @@ import {Beat} from '../../domain/beat'; import {NgFor} from '@angular/common'; import {StepLengths} from './models/step-lengths'; import {Genre} from "../../domain/genre"; -import { ActivatedRoute } from '@angular/router'; +import {ActivatedRoute} from '@angular/router'; import IManageGenres, {IManageGenresToken} from "../../domain/ports/secondary/i-manage-genres"; import {Subject} from "rxjs"; +import {BpmInputComponent} from "../bpm-input/bpm-input.component"; @Component({ selector: 'sequencer', templateUrl: './sequencer.component.html', styleUrls: ['./sequencer.component.scss'], standalone: true, - imports: [NgFor] + imports: [NgFor, BpmInputComponent] }) export class SequencerComponent implements OnInit { beat = {} as Beat; @@ -78,5 +79,18 @@ export class SequencerComponent implements OnInit { protected readonly StepLengths = StepLengths; protected readonly Math = Math; + + changeBeatBpm($event: number) { + const isPlaying = this.soundService.isPlaying; + this.soundService.setBpm($event); + this.soundService.generateLoopBuffer().then( + () => { + if(isPlaying) + this.soundService.play(); + }, + () => { + } + ); + } } diff --git a/frontend/src/app/services/sound/sound.service.ts b/frontend/src/app/services/sound/sound.service.ts index c5c80ac..5de8c8f 100644 --- a/frontend/src/app/services/sound/sound.service.ts +++ b/frontend/src/app/services/sound/sound.service.ts @@ -9,6 +9,8 @@ import {LoadingBarService} from '@ngx-loading-bar/core'; providedIn: 'root' }) export class SoundService { + static maxBpm = 300; + static minBpm = 30; bpm: number = 120; isPlaying: boolean = false; index: number = 0; @@ -39,7 +41,7 @@ export class SoundService { this.stepNumber ); } - this.playSound(this.loopBuffer); + this.play(); } else { this.pause(); } @@ -118,4 +120,19 @@ export class SoundService { setStepNumber(length: number) { this.stepNumber = length; } + + async generateLoopBuffer(): Promise { + this.loopBuffer = await this.soundGeneratorService.getRenderedBuffer( + this.tracks, + this.samples, + this.bpm, + this.stepNumber + ); + } + + play() : void { + if(!this.loopBuffer) + return; + this.playSound(this.loopBuffer); + } }