带有快速枚举的简单音乐音调值

我从事的乐器应用程序通常包含音乐理论部分。 这可能是一个简单的大尺度或更复杂的算法。 通常,我最终只使用MIDI音符编号来获取音高值。

结果最终看起来像:

let cMajor = [48, 50, 52, 53, 55, 57, 59] 

我决定做一些工作来制作一个可以在项目中重复使用的简单模式,以使代码更易于阅读。

代码很短,所以我将从此开始:

 enum Pitch: Int { 
case C = 0, Cs, D, Ds, E, F, Fs, G, Gs, A, As, B
}

现在我可以写:

 let cMajor :[Pitch] = [.C,.D,.E,.F,.G,.A,.B] 

[Pitch]的显式类型允许我只键入.G而不是Pitch.G ,我认为它看起来更好。

概念

理想情况下,我将能够将音高作为偶然的音符名称来编写。 这些值看起来就像C♯D♭ 。 这些值将是类型安全的,并且像原始类型一样起作用,如果我错误地输入了假间距,则会导致编译器抛出错误。

设计决策

当我经历一些选择时,我意识到我将不得不做出一些妥协。

没有Unicode意外情况: 不在Swift支持的Unicode范围内。 有替代符号,但默认字体未包含这些替代符号,因此代码可移植性成为一个问题。 我为竖琴选择了一个简单s后缀。

没有枚举别名: Swift枚举不支持重复值或别名。 我不能优雅地将C♯设置为等于D♭ 。 我选择保留一切。 在我的脑海里,我称之为“调谐器格式”,因为我在吉他调音器上的剪辑仅在其LCD屏幕上使用了锐器。

最近的音高

音高名称的另一个重要特征是它们是圆形的。 即使B在列表的相对两端,也仅是C一步。

我创建了一种简单的方法来查找最接近的音高。

 extension Pitch { 
func nearest(_ destination: Pitch) -> Int {
let right = (12 - self.rawValue + destination.rawValue) % 12
let left = -(self.rawValue + 12 - destination.rawValue) % 12
return abs(right) <= abs(left) ? right : left
}
}

贪婪地向右看,向左看该值,然后将其减小到八度以下,因为音高不超过12步。

这样一来,值就可以以半音为单位计算第二个音高的距离和方向。 在使用中,它看起来像这样:

 Pitch.C.nearest(.B) // -1 
Pitch.B.nearest(.C) // 1
Pitch.C.nearest(.D) // 2
Pitch.C.nearest(.G) // -5

实际使用

自从搬到圣路易斯以来,我一直在演奏班卓琴。 我经常忘记的一件事是如何移入和移出备用调音。 使用此值类型和方法,可以很容易地用代码进行演示。

我最常使用的两个调音是开G和双C。表示如下:

 var openG :[Pitch] = [.G,.D,.G,.B,.D] 
var doubleC :[Pitch] = [.G,.C,.G,.C,.D]

现在,如果我想从空G移到双C,我仅使用几种功能方法:

 let adjust = zip(openG, doubleC).map{$0.nearest($1)} 

这将获取openG中的每个音高,将其与doubleC中的相应音高配对,然后计算音高之间的距离。

这样产生:

 [0, 1, 0, -2, 0] 

这意味着第二个字符串上升1步,第四个字符串下降2。

而且我也可以将其反转以从双C转到G:

 let adjust = zip(doubleC, openG).map{$0.nearest($1)} 
[0, -1, 0, 2, 0]

扩展使用

音高名称实际上只是一个基础。 我可以想到很多非常有趣的应用程序,例如实现算术,MIDI映射,比例过滤器,键签名和字符串便捷方法。

实际上,可以将一些简单的行粘贴到项目中,并让以后的开发人员轻松理解。