Skip to content

Commit

Permalink
Accords + Patterns #6
Browse files Browse the repository at this point in the history
  • Loading branch information
Bludwarf committed Mar 15, 2024
1 parent 71c3cc8 commit 8021dca
Show file tree
Hide file tree
Showing 13 changed files with 362 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/app/fretboard/fretboard.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[attr.data-fret]="line.fret"
[attr.data-string]="note.string.name"
[attr.data-note]="note.value"
[class.active]="currentNote?.equals(note)"
[attr.data-degree]="key && note.degreeIn(key).value"
[attr.data-mode]="key && note.modeIn(key).name"
[attr.title]="key &&
Expand Down
2 changes: 1 addition & 1 deletion src/app/fretboard/fretboard.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
/* box-shadow: 0 -2mm 0 0 #FFF;*/
/*}*/

[data-note]:hover {
[data-note]:hover, [data-note].active {
color: white;
text-shadow: 0 0 1ex #000;
border: solid 1pt #fff;
Expand Down
3 changes: 3 additions & 0 deletions src/app/fretboard/fretboard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export class FretboardComponent implements OnInit, OnChanges {
@Input()
key? = new Key(Note.fromName('C'), Mode.fromName('I'))

@Input()
currentNote?: Note

fretboard?: Fretboard;

ngOnInit(): void {
Expand Down
24 changes: 24 additions & 0 deletions src/app/notes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Chord, Chords } from "./notes";
import { Time } from "./time";

describe('Chords', () => {

it('should create from | Gm | F | Eb | D |', () => {
const chords = Chords.fromAsciiChords('| Gm | F | Eb | D |')
expect(chords.length).toBe(4)
expect(chords.getChordAt(Time.fromValue('0:0'))).toEqual(new Chord('Gm'))
expect(chords.getChordAt(Time.fromValue('1:0'))).toEqual(new Chord('F'))
expect(chords.getChordAt(Time.fromValue('2:0'))).toEqual(new Chord('Eb'))
expect(chords.getChordAt(Time.fromValue('3:0'))).toEqual(new Chord('D'))
});

it('should create from | Gm F | Eb D |', () => {
const chords = Chords.fromAsciiChords('| Gm F | Eb D |')
expect(chords.length).toBe(4)
expect(chords.getChordAt(Time.fromValue('0:0'))).toEqual(new Chord('Gm'))
expect(chords.getChordAt(Time.fromValue('0:2'))).toEqual(new Chord('F'))
expect(chords.getChordAt(Time.fromValue('1:0'))).toEqual(new Chord('Eb'))
expect(chords.getChordAt(Time.fromValue('1:2'))).toEqual(new Chord('D'))
});

});
141 changes: 139 additions & 2 deletions src/app/notes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Time } from "./time";

export const NOTE_NAMES = [
'C',
'C#',
Expand Down Expand Up @@ -26,9 +28,14 @@ class Mod12Value {
if (value === -1) throw new Error('invalid name : ' + name);
return value;
}

equals(note: Note): boolean {
return this.value === note.value;
}
}

export class Note extends Mod12Value {

constructor(value: number) {
super(value);
}
Expand Down Expand Up @@ -56,6 +63,25 @@ export class Note extends Mod12Value {
modeIn(key: Key): Mode {
return new Mode(key.mode.value + this.value - key.note.value);
}

override toString(): string {
return NOTE_NAMES[this.value]
}
}

export namespace Note {
export const C = Note.fromName('C')
export const Cs = Note.fromName('C#')
export const D = Note.fromName('D')
export const Eb = Note.fromName('Eb')
export const E = Note.fromName('E')
export const F = Note.fromName('F')
export const Fs = Note.fromName('F#')
export const G = Note.fromName('G')
export const Ab = Note.fromName('Ab')
export const A = Note.fromName('A')
export const Bb = Note.fromName('Bb')
export const B = Note.fromName('B')
}

export const MODE_NAMES = [
Expand Down Expand Up @@ -89,12 +115,123 @@ export class Mode extends Mod12Value {

/** Tonalité */
export class Key {
constructor(readonly note: Note, readonly mode: Mode) {}
constructor(readonly note: Note, readonly mode: Mode) { }
}


export class Degree extends Mod12Value {
constructor(value: number) {
super(value);
}
}
}

export class Chord {

readonly root: Note

constructor(
readonly name: string, // TODO pour l'instant on fait simple
root?: Note,
) {
this.root = Chord.getRootFromName(name)
}

static getRootFromName(name: string): Note {
// TODO faire une vraie détection
if (NOTE_NAMES.includes(name)) {
return Note.fromName(name)
}
// TODO gérer '#' -> 's'
switch (name) {
case 'Gm':
return Note.G
}
throw new Error('Cannot find root from ' + name)
}

toString(): string {
return this.name
}

}

export namespace Chord {
export const Gm: Chord = new Chord("Gm", Note.G)
}

export type AsciiChords = string

export class Chords {

constructor(
private readonly chordsByTime: [Time, Chord][] = [], // TODO trier par time asc
) { }

static fromAsciiChords(asciiChords: AsciiChords): Chords {

console.log('asciiChords', asciiChords)

const barGroups = asciiChords.split('|').slice(1, -1).map(x => x.trim())
if (barGroups.length === 0) {
throw new Error('Cannot find bars in AsciiChords : ' + asciiChords)
}

const chords = new Chords()

let time = Time.fromValue(0)
barGroups.forEach(barAsciiChords => {

const chordGroups = barAsciiChords.split(' ')

if (chordGroups.length === 1) {
const duration = Time.fromValue('1m')

const chord = new Chord(barAsciiChords)
console.log(time.toBarsBeatsSixteenths(), chord.name)
chords.setChordAt(time, chord)

time = time.add(duration)
return
}

if (chordGroups.length === 2) {
const duration = Time.fromValue('2n') // TODO valable uniquement en 4/4

chordGroups.forEach(chordGroup => {
const chord = new Chord(chordGroup)
console.log(time.toBarsBeatsSixteenths(), chord.name)
chords.setChordAt(time, chord)

time = time.add(duration)
})

return
}

throw new Error('Cannot split chords in bar : ' + barAsciiChords)

})

return chords
}

setChordAt(time: Time, chord: Chord) {
// TODO trier par time asc
this.chordsByTime.push([time, chord])
}

getChordAt(time: Time): Chord | undefined {
// TODO factoriser avec getCurrentPattern
const reversedChordsByTime = [... this.chordsByTime].reverse()
const chordAtTime = reversedChordsByTime.find(([chordTime]) => chordTime.isBeforeOrEquals(time));
return chordAtTime?.[1]
}

get length(): number {
return this.chordsByTime.length
}

toString(): string {
return this.chordsByTime.map(([chordTime, chord]) => `${chordTime.toBarsBeatsSixteenths()} ${chord}`).join('\n')
}
}
7 changes: 6 additions & 1 deletion src/app/rythm-sandbox/rythm-sandbox.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<input type="range" min="0" max="100" step="1" [value]="progress" (change)="setProgress($event)" />
<br/>

<!-- Tonalité -->
Gm

<app-structure [structure]="structure" [currentPatternInStructure]="currentPatternInStructure" (clickPatternInStructure)="onClickPatternInStructure($event)"></app-structure>
Expand All @@ -25,14 +26,18 @@
<br>
</ng-container>

<!-- Bombarde dès 1.1.1 mais groupe à 5.1.1 -->
<!-- TODO : Bombarde dès 1.1.1 mais groupe à 5.1.1 -->

{{currentChord}}
<br/>

<ng-container *ngIf="currentPatternInStructure && currentPatternInStructure.pattern && currentPatternInStructure.pattern.name == 'Partie bombarde'">
{{currentPatternInStructure.pattern.name}} : | Gm F | Eb D |
<app-fretboard
[lowestFret]="1"
[fretsCount]="6"
[key]="currentPatternInStructure.pattern.key"
[currentNote]="currentChord?.root"
></app-fretboard>
<app-rythm-bar
[number]="1"
Expand Down
33 changes: 27 additions & 6 deletions src/app/rythm-sandbox/rythm-sandbox.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Time } from '../time';
import { WrapMarker } from '../wrap-marker';
import { PatternInStructure } from '../structure/pattern/pattern-in-structure';
import { FretboardComponent } from '../fretboard/fretboard.component';
import { Key, Note } from '../notes';
import { Chord, Key, Note } from '../notes';

// TODO comment avoir la durée en secondes du sample ?
// On utilise pour l'instant le fichier DIDAFTA PETIT PAPILLON Master Web 24bit 48Khz_02-01.wav
Expand Down Expand Up @@ -108,6 +108,7 @@ export class RythmSandboxComponent {
events: RythmBarEvent[] = RythmBarEvent.fromEach(events);

currentPatternInStructure?: PatternInStructure;
currentChord?: Chord;

private player?: Tone.Player;

Expand All @@ -119,7 +120,7 @@ export class RythmSandboxComponent {
constructor(
private readonly changeDetectorRef: ChangeDetectorRef,
) {
console.log('Events chargés depuis le JSON', events);
// console.log('Events chargés depuis le JSON', events);

Tone.Transport.schedule(function (time) {
console.log('Première mesure')
Expand Down Expand Up @@ -163,9 +164,27 @@ export class RythmSandboxComponent {

await Tone.loaded() // évite les erreurs de buffer

const couplet = new Pattern('Couplet', new Time(Tone.Time('2m')), undefined, new Key(new Note(7), new Note(9)))
const bombarde = new Pattern('Partie bombarde', new Time(Tone.Time('2m')), 'B', new Key(new Note(7), new Note(9)))
const refrain = new Pattern('Refrain', new Time(Tone.Time('4m')), undefined, new Key(new Note(7), new Note(9)))
const key = new Key(new Note(7), new Note(9))

const bombarde = Pattern.fromData({
name: 'Partie bombarde',
duration: '2m', // TODO pourrait être déduit de chords
initial: 'B',
key,
chords: '| Gm F | Eb D |',
})
const couplet = Pattern.fromData({
name: 'Couplet',
duration: '2m',
key,
chords: '| Gm F | Eb D |', // | Gm F | Eb D* | (*Bb sur le 4e pattern)
})
const refrain = Pattern.fromData({
name: 'Refrain',
duration: '4m',
key,
chords: '| Bb | F | C | Gm |',
})

const coupletBlock = [couplet, couplet]
const bombardeBlock = [bombarde, bombarde]
Expand Down Expand Up @@ -232,7 +251,9 @@ export class RythmSandboxComponent {

if (this.structure && wrappedTime) {
const changePatternFasterDelay = Time.fromValue(0) // Time.fromValue('4n') // TODO trop bizarre à l'affichage de la section courante, mais ok pour affichage partoche
this.currentPatternInStructure = this.structure.getPatternInStructureAt(wrappedTime.add(changePatternFasterDelay))
const delayedWrappedTime = wrappedTime.add(changePatternFasterDelay);
this.currentPatternInStructure = this.structure.getPatternInStructureAt(delayedWrappedTime)
this.currentChord = this.currentPatternInStructure?.getChordAt(delayedWrappedTime)
}

this.changeDetectorRef.detectChanges();
Expand Down
7 changes: 7 additions & 0 deletions src/app/structure/pattern/pattern-in-structure.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Chord } from "../../notes";
import { Time } from "../../time";
import { Structure } from "../structure";
import { Pattern } from "./pattern";
Expand All @@ -19,4 +20,10 @@ export class PatternInStructure {
return this.pattern.initial ?? this.pattern.name.charAt(0)
}

getChordAt(time: Time): Chord | undefined {
const relativeTime = time.relativeTo(this.startTime)
console.log('getChordAt', time.toBarsBeatsSixteenths(), relativeTime.toString(), '\n' + this.pattern.chords?.toString())
return this.pattern.chords?.getChordAt(relativeTime)
}

}
22 changes: 21 additions & 1 deletion src/app/structure/pattern/pattern.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as Tone from 'tone'
import { Time } from '../../time';
import { Key } from '../../notes';
import { AsciiChords, Chords, Key } from '../../notes';
import { TimeValue } from 'tone/build/esm/core/type/TimeBase';

export class Pattern {

Expand All @@ -9,7 +10,26 @@ export class Pattern {
readonly duration: Time,
readonly initial?: string,
readonly key?: Key,
readonly chords?: Chords,
) {
}

static fromData(data: PatternInitData): Pattern {
return new Pattern(
data.name,
new Time(Tone.Time(data.duration)),
data.initial,
data.key,
data.chords ? Chords.fromAsciiChords(data.chords) : undefined
);
}

}

export interface PatternInitData {
name: string
duration: TimeValue
initial?: string
key?: Key
chords?: AsciiChords
}
Loading

0 comments on commit 8021dca

Please sign in to comment.