钢琴和弦小工具(四)显示和弦
前言
人,不但要有科学技术,而且还要,文化,艺术,跟音乐。 ————钱学森
遗留的subKeys()方法
我们继续来看下之前遗留的subKeys()方法。这个方法在前文中,用于给黑键和白键分别布局。
private subKeys(isWhite: boolean): Key[] { // 根据入参的布尔值,返回黑键或者白键的对象数组subKeys。Key类声明见下文。
const subKeys: Array<Key> = new Array<Key>()
let id = 0 // 这个id很关键,用于UI布局里面,所有的按键按照半音间隔依次排列,这个id就是可见按键的序号。
for (let i = 0; i < 27; i++) { // 两个八度的按键循环,算上fake black,一共28个按键。
if (isWhite) { // 如果需要返回白键
if (i % 2 === 0 ) { // 第一个按键是白键,所以判断循环计数为偶数。
subKeys.push(new Key(id, KeyType.white, this.isSelected(id), this.isChord(id)))
}
} else { // 如果需要返回黑键
if (i % 2 === 1 ) { // 根据半音间隔排列,第二个按键是黑键,所以判断循环计数为奇数。
let keyType = KeyType.black
if (this.isFakeBlack(i)) { // mi fa si do 中间的空挡(fake black)。此处keyType需要做特殊处理。
keyType = KeyType.fakeBlack
}
subKeys.push(new Key(id, keyType, this.isSelected(id), this.isChord(id)))
}
}
if (!this.isFakeBlack(i)) { // 每次循环最后要判断一次当前是否遍历到的是fake black,如果不是fake black,即为实际显示的按钮,那么id计数加一。如此,返回的可见黑键id即为正常累加。
id++
}
}
return subKeys
}
export class Key {
public id: number
public keyType: KeyType // 白键,黑键还是fake black
public isSelected: boolean // 是否被选中
public isChord: boolean // 是否为和弦按键
constructor(
id: number,
keyType: KeyType,
isSelected: boolean,
isChord: boolean
) {
this.id = id
this.keyType = keyType
this.isSelected = isSelected
this.isChord = isChord
}
}
明白了subKeys()实现之后,可以回过头再理解一下前文中的isFakeBlack()方法
private isFakeBlack(index: number): boolean {
return (index + 1) % 14 === 6 || (index + 1) % 14 === 0
}
subKeys()的循环是遍历所有包括白色和黑色的按键,所以我们在图里按顺序给所有按键编号,便于理解。我们把index加一再判断,符合日常的编号习惯。而且这样正好把一个八度里面所有显示和不显示的按键都编上了号,一共14个,下一个八度正好重新循环。可以看到把index+1模除6和14的时候,这种情况是需要隐藏的按键。
显示和弦
万事俱备,只欠东风。至此,其实代码在之前都已经展示过了。但还需要梳理一下,显示和弦的流程。
首先在成员属性里面向currentChord赋值。
@Prop currentChord: number[]
这个数字的数组含义为,和弦中所有按键之间的半音间隔数量。在之前布局的时候,subKeys()方法的核心思想是把所有按键按照间隔顺序编号。所以我们以大三和弦举例,根音为do时,需要的按键序号id(这里的序号和上文中包含fake black按键的序号需要注意区分)就是0,4,7。但这个数组不能表达按键的序号,表达的应该是按键之间的半音间隔数量。因为根音还会有re或者mi和其他任意按键。
这是之前提到的isChord()方法,用于判断某个按键是否为和弦。每次点击按键以后,会给this.currentKey赋值当前按键的id,因为按下的按钮需要给粉色,所以遍历到当前按钮时也会返回false。只有根音以外的其他按键才会返回true。
private isChord(index: number): boolean { // index为布局时遍历到的每一个按键
if (this.currentKey === -1) {
return false
}
const currentKey_ = this.currentKey % 12 // 模除12,只在第一个八度中显示和弦按键
return this.currentChord.includes(index - currentKey_) // 把遍历到的按键序号index和当前按下的按键序号currentKey_相减,得到按键之间的半音间隔,如果这个间隔包含在数组中,即为和弦按键。
}
理解了和弦数据表达以后,我们得出大三和弦的数组即为0,4,7。以此类推,小三和弦为0,3,7。七和弦为0,4,7,10。这样,任意和弦的数据在之后添加的时候就非常简单了。
结尾
这个应用看似功能简单,实则在实现的时候,有很多细节需要考虑。主要难点在于布局的序号和实际可见按键序号的分离。布局的序号有利于布局循环,可见按键的序号用于和弦数据的表达。
另外,我认为人类的知识应该是融会贯通的,而不是各自成为孤岛。科学和艺术应该是相互融合的,比如黄金分割数会很自然的给人带来美感。
没有艺术的科学是没有灵魂的,没有科学的艺术是杂乱的。
感谢阅读。