Add transposing and use a pipe to format the chords

This commit is contained in:
Simon Hanna 2018-08-30 16:50:07 +02:00
parent a083bea652
commit f04941bf74
5 changed files with 219 additions and 7 deletions

View File

@ -28,6 +28,8 @@ import { FormsModule } from '@angular/forms';
import { ChordViewComponent } from './components/chord-view/chord-view.component';
import { StageViewComponent } from './components/stage-view/stage-view.component';
import { MainViewComponent } from './components/main-view/main-view.component';
import { ChordProPipe } from './components/chord-view/chordpro.pipe';
@NgModule({
declarations: [
@ -38,7 +40,8 @@ import { MainViewComponent } from './components/main-view/main-view.component';
OpenLPSlidesComponent,
ChordViewComponent,
StageViewComponent,
MainViewComponent
MainViewComponent,
ChordProPipe
],
imports: [
BrowserModule,

View File

@ -4,11 +4,9 @@
<span *ngFor="let tag of tags" [class.active]="tag.active">{{ tag.text }}</span>
</div>
<div class="container">
<div class="slide currentSlide" [innerHTML]="currentSlides[activeSlide]?.chords_text">
</div>
<div class="slide currentSlide song" [innerHTML]="chordproFormatted(currentSlides[0])|chordpro:transpose"></div>
<div class="nextSlides">
<div class="slide" [class.first]="slide.first_slide_of_tag" *ngFor="let slide of nextSlides" [innerHTML]="slide.chords_text">
</div>
<div class="slide song" [class.first]="slide.first_slide_of_tag" *ngFor="let slide of nextSlides" [innerHTML]="chordproFormatted(slide)|chordpro:transpose"></div>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { OpenLPService } from '../../openlp.service';
import { Slide } from '../../responses';
import { Observable } from 'rxjs';
@ -7,7 +7,9 @@ import { StageViewComponent } from '../stage-view/stage-view.component';
@Component({
selector: 'app-chord-view',
templateUrl: './chord-view.component.html',
styleUrls: ['./chord-view.component.scss', '../overlay.scss']
styleUrls: ['./chord-view.component.scss', '../overlay.scss', './chordpro.scss'],
encapsulation: ViewEncapsulation.None // needed for the chords to be displayed
})
export class ChordViewComponent extends StageViewComponent {
transpose: number = 0;
@ -19,4 +21,18 @@ export class ChordViewComponent extends StageViewComponent {
transposeDown(): void {
this.transpose--;
}
chordproFormatted(slide: Slide): string {
if (!slide) {
return '';
}
let chordpro: string = slide.chords_text;
chordpro = chordpro.replace(/<span class="\w*\s*\w*">/g, '');
chordpro = chordpro.replace(/<span>/g, '',);
chordpro = chordpro.replace(/<\/span>/g, '');
chordpro = chordpro.replace(/<strong>/g, '[');
chordpro = chordpro.replace(/<\/strong>/g, ']');
return chordpro;
}
}

View File

@ -0,0 +1,174 @@
/**
* ChordProPipe
*
* A pipe for angular 2/4 that translate ChordPro-formatted text into an HTML representation, to be used in conjunction with a set of styles
* for proper display.
*
* If you make improvements, please send them to me for incorporation.
*
* @author David Quinn-Jacobs (dqj@authentrics.com)
* @licence Use this in any way you like, with no constraints.
*/
import { Pipe, PipeTransform } from '@angular/core';
import { MAT_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY } from '@angular/material';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Pipe({ name: 'chordpro' })
export class ChordProPipe implements PipeTransform {
private readonly MAX_HALF_STEPS = 11;
constructor(private sanitizer: DomSanitizer) {
this.notesSharpNotation['german'] = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','H'];
this.notesFlatNotation['german'] = ['C','Db','D','Eb','Fb','F','Gb','G','Ab','A','B','H'];
this.notesSharpNotation['english'] = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
this.notesFlatNotation['english'] = ['C','Db','D','Eb','Fb','F','Gb','G','Ab','A','Bb','B'];
this.notesSharpNotation['neo-latin'] = ['Do','Do#','Re','Re#','Mi','Fa','Fa#','Sol','Sol#','La','La#','Si'];
this.notesFlatNotation['neo-latin'] = ['Do','Reb','Re','Mib','Fab','Fa','Solb','Sol','Lab','La','Sib','Si'];
}
private keys = [
{ name: 'Ab', value: 0 },
{ name: 'A', value: 1 },
{ name: 'A#', value: 2 },
{ name: 'Bb', value: 2 },
{ name: 'B', value: 3 },
{ name: 'C', value: 4 },
{ name: 'C#', value: 5 },
{ name: 'Db', value: 5 },
{ name: 'D', value: 6 },
{ name: 'D#', value: 7 },
{ name: 'Eb', value: 7 },
{ name: 'E', value: 8 },
{ name: 'F', value: 9 },
{ name: 'F#', value: 10 },
{ name: 'Gb', value: 10 },
{ name: 'G', value: 11 },
{ name: 'G#', value: 0 }
];
notesSharpNotation = {};
notesFlatNotation = {};
/**
* @var chordRegex Expression used to determine if given line contains a chord.
* @type {RegExp}
*/
private chordRegex = /\[([^\]]*)\]/;
/**
* Pipe transformation for ChordPro-formatted song texts.
* @param {string} song
* @param {number} nHalfSteps
* @returns {string}
*/
transform(song: string, nHalfSteps: number): string|SafeHtml {
let transformed = song;
try {
if (song !== undefined && song) {
return this.sanitizer.bypassSecurityTrustHtml(this.parseToHTML(song, nHalfSteps));
}
else {
return transformed;
}
}
catch (exception) {
console.warn('chordpro translation error', exception);
}
}
chordRoot(chord) {
let root = '';
let ch2 = '';
if (chord && chord.length > 0) {
root = chord.substr(0, 1);
if (chord.length > 1) {
ch2 = chord.substr(1, 1);
if (ch2 === 'b' || ch2 === '#') {
root += ch2;
}
}
}
return root;
}
restOfChord(chord) {
let rest = '';
let root = this.chordRoot(chord);
if (chord.length > root.length) {
rest = chord.substr(root.length);
}
return rest;
}
/**
* Transpose the given chord the given (positive or negative) number of half steps.
* @param {string} chordRoot
* @param {number} nHalfSteps
* @returns {string}
*/
transposeChord(chordRoot, nHalfSteps) {
let pos = -1;
for (let i = 0; i < this.keys.length; i++) {
if (this.keys[i].name === chordRoot) {
pos = this.keys[i].value;
break;
}
}
if (pos >= 0) {
pos += nHalfSteps;
if (pos < 0) {
pos += this.MAX_HALF_STEPS;
}
else if (pos > this.MAX_HALF_STEPS) {
pos -= this.MAX_HALF_STEPS + 1;
}
for (let i = 0; i < this.keys.length; i++) {
if (this.keys[i].value === pos) {
return this.keys[i].name;
}
}
}
return chordRoot;
}
/**
* Parse a string containing a ChordPro-formatted song, building an array of output HTML lines.
*
* @param {number} nHalfSteps
* @param {string} song
*/
private parseToHTML(song: string, nHalfSteps = 0): string {
// we are currently receiving html, we need to replace that stuff,
// becuase it gets messed up when a chord is placed on it..
// shouldn't be relevant if we actually get chordpro format
song = song.replace(/&ndash;/g, '-');
console.log('parsing: ', song);
let comp = this;
if (!song) {
return '';
}
let chordText: string = '';
let lastChord: string = '';
song.split(comp.chordRegex).forEach((part, index) => {
if (index % 2 == 0) {
// text
if (lastChord) {
chordText += `<span data-chord="${lastChord}">${part.substring(0, 1)}</span>${part.substring(1)}`
lastChord = '';
} else {
chordText += part;
}
} else {
// chord
lastChord = part.replace(/[[]]/, '');
if (nHalfSteps !== 0) {
let chordRoot = comp.chordRoot(lastChord);
let newRoot = comp.transposeChord(chordRoot, nHalfSteps);
lastChord = newRoot + comp.restOfChord(lastChord);
}
}
});
return chordText;
}
}

View File

@ -0,0 +1,21 @@
.song {
white-space: pre-wrap;
line-height: 2;
span[data-chord]:before {
position: relative;
top: -1em;
display: inline-block;
content: attr(data-chord);
width: 0;
color: yellow;
}
}
.nextSlides {
.song {
span[data-chord]:before {
color: gray;
}
}
}