musicxml2json-piano
Version:
MusicXML to JSON for piano.
190 lines (156 loc) • 4.38 kB
text/typescript
import { has, isArray, isEmpty } from 'lodash'
import { calculateRelativePosition, noteToMidi } from '@/utils'
import {
Articulation,
Beam,
Clef,
Dot,
Notations,
Note as NoteT,
NoteData,
NoteType,
NoteView,
NoteXML,
Slur,
Stem,
Tied,
Time,
TimeModification,
Tuplet
} from '@/types'
type PropsType = {
id: string
staff?: Clef
xmlData?: NoteXML
}
interface NoteInterface extends NoteT {
appendData(data: NoteData): void
getData(noteXML: NoteXML, staff: Clef): NoteData | null
}
export default class Note implements NoteInterface {
public beam: Beam[] | null = null
public data: NoteData[] | null = null
public dot: Dot | null = null
public id: string
public name: string = ''
public notations: Notations = {
articulations: null,
slur: null,
tied: null,
tuplet: null
}
public stem: Stem | null = null
public time: Time | null = null
public timeModification: TimeModification | null = null
public type: NoteType
public view: NoteView
constructor ({ id, xmlData }: PropsType) {
this.id = id
if (xmlData) {
this.beam = this.getBeam(xmlData)
this.dot = this.getDot(xmlData)
this.notations = this.getNotations(xmlData)
this.stem = this.getStem(xmlData)
this.timeModification = this.getTimeModification(xmlData)
this.type = this.getType(xmlData)
this.view = this.getView(xmlData)
} else {
this.view = 'rest'
this.type = 'whole'
}
}
private getArticulations(noteXML): Articulation | null {
if (has(noteXML, 'notations.articulations.staccato')) { // 跳音
return 'staccato'
}
if (has(noteXML, 'notations.articulations.accent')) { // 重音
return 'accent'
}
return null
}
private getBeam(noteXML: NoteXML): Beam[] | null {
const beam = noteXML.beam
if (!beam) return null
return isArray(beam) ? beam.map(item => item['#text']) : [beam['#text']]
}
getData(noteXML: NoteXML, staff: Clef): NoteData | null {
const { pitch } = noteXML
if (isEmpty(pitch)) {
return null
}
// 临时标记
let accidental = null
if (has(noteXML, 'accidental')) {
accidental = noteXML.accidental
}
// 计算音符位置
const { step, octave, alter } = pitch
const position = calculateRelativePosition(step, octave, staff) || 0
return {
accidental,
legerLine: position < -1 || position > 5,
midiCode: noteToMidi(`${step}${alter ? '#' : ''}`, octave),
pitch: {
step,
octave,
alter: alter || 0
},
position
}
}
private getDot(noteXML: NoteXML): Dot | null {
if (!has(noteXML, 'dot')) {
return null
}
const { dot } = noteXML
if (isArray(dot)) {
return dot.length >= 2 ? 'doubleDot' : 'dot'
}
return 'dot'
}
private getView(noteXML: NoteXML): NoteView {
return has(noteXML, 'rest') ? 'rest' : 'single'
}
private getNotations(noteXML: NoteXML): Notations {
return {
articulations: this.getArticulations(noteXML),
slur: this.getSlur(noteXML),
tied: this.getTied(noteXML),
tuplet: this.getTuplet(noteXML)
}
}
private getSlur(noteXML: NoteXML): Slur {
return noteXML.notations?.slur?._type ?? null
}
private getStem(noteXML: NoteXML): Stem {
return noteXML.stem ?? null
}
private getTied(noteXML: NoteXML): Tied | null {
const tied = noteXML.notations?.tied
if (!tied) return null
return isArray(tied) ? tied[0]._type : tied._type
}
private getTimeModification(noteXML: NoteXML): TimeModification | null {
const timeMod = noteXML['time-modification']
if (!timeMod) return null
const { 'actual-notes': actualNotes, 'normal-notes': normalNotes } = timeMod
return actualNotes && normalNotes ? { actualNotes, normalNotes } : null
}
private getTuplet(noteXML: NoteXML): Tuplet | null {
const tuplet = noteXML.notations?.tuplet
if (!tuplet) return null
return isArray(tuplet) ? tuplet[0]._type : tuplet._type
}
private getType(noteXML: NoteXML): NoteType {
return noteXML.type
}
appendData(data: NoteData) {
if (!this.data) {
this.data = []
}
this.data?.push(data)
}
appendTime(start: number, duration: number) {
this.time = { start, duration, end: start + duration }
}
}