我想写一个algorythm,如果不是所有可能的音符被覆盖的话,在给定的八度音程中建立常规的3键钢琴和弦进行到下一个。例如:
Cmaj键将提供其中所有音符/和弦的进展,因为开始音符是八度音阶的开始,它将在下一个C结束。但是如果我从同一个八度音阶的B音符开始,它将结束与B在下一个也。
我想为主要和次要音阶构建它,能够在将来扩展它为7和9型和弦。
这不是作业,我想使用c#然后在f#中重新编写它以更多地学习语言。
修改
我的问题是: 我应该为八度音程(C到C)使用什么数据结构:LinkedList List或者这可能需要完全不同的结构?
Edit2:
所以如果我们索引这样的笔记,我不确定它是否是正确的方法:
0 1 2 3 4 5 6 7 8 9 10 11 12
Input: Note = C (0), Scale = Maj
Output: 0 4 7, 2 5 9, 4 7 12, etc.
答案 0 :(得分:3)
对此进行建模的最简单方法可能是使用midi note mapping的概念,因为枚举键并且来自给定根的第一个反转三元组将是
root, root + 4, root + 7
下一次反转将是
root + 4, root + 7, root + 12
下一次反转将是
root + 7, root + 12, root + 16
其中root是你的root的midi音符编号。
事实上,考虑到第一次反转的和弦,通过删除第一个条目,将它放在最后并添加12来生成所有其他反转是微不足道的。所以你的和弦真的会开始看起来像这样:
public int GetChord(ChordName chord)
{
switch (chord) {
case ChordName.Major: return new int[] { 0, 4, 7 };
case ChordName.Minor: return new int[] { 0, 3, 7 };
case ChordName.Augmented: return new int[] { 0, 4, 8 };
case ChordName.Dominant7: return new int[] { 0, 4, 7, 10 };
case ChordName.Maj7: return new int[] { 0, 4, 7, 11 };
// etc
}
然后从这里返回的任何东西(并且可能使用List会更好),你可以编写一个返回每个反转的IEnumerable。然后你将root的值添加到输出和ta-da!你有你的和弦,现在很容易输出,好吧,midi。
public int[] InvertChord(int[] chord)
{
int[] inversion = new int[chord.Length];
for (int i = 1; i < chord.Length; i++) {
inversion[i-1] = chord[i];
}
inversion[inversion.Length-1] = chord[0] + 12;
return inversion;
}
public int[][] ChordAndAllInversions(int[] chord)
{
int[][] inversions = new int[chord.Length][];
inversions[0] = chord;
for (int i=1; i < chord.Length; i++) {
inversions[i] = InvertChord(inversions[i - 1]);
}
return inversions;
}
答案 1 :(得分:3)
几年前,当我在Java中执行此操作时,我创建了以下类:
Note
- 表示任何给定八度音阶中的12个不同音符中的每一个([C,C#/ Db,D,D#/ Eb,E,F,F#/ Gb,G,G#/ Ab,A ,A#/ Bb,B])Octave
- 存储一个整数来区分其他八度音阶Pitch
- 存储Note和Octave ScalePattern
- 编码从一个音高到下一个音高的半步数(例如,大音阶为[0,2,4,5,7,9,11,12])Scale
- 存储初始注释和ScalePattern 此设计也可以轻松定义和使用ChordPatterns
。
答案 2 :(得分:2)
顺便说一句,我喜欢音乐理论,数学和F#,所以我无法抗拒探索这个问题。
起初,我尝试了一个纯粹的功能性解决方案,仅使用模块,F#函数和基本数据结构,但这很快就失去控制(因为我寻求一些相当雄心勃勃的目标,包括支持任意比例,而不仅仅是“主要“和”未成年人“)。接下来是我在F#中使用面向对象的“在媒体中编程”的第一次“认真”努力。正如我之前所说,我认为我可以避免这种情况,但事实证明在F#中使用面向对象实际上非常好,并且不会过多地破坏美观和简洁(特别是当我们忽视其他.NET语言的可消费性时) )。
首先,我将使用一些实用功能:
module MusicTheory.Utils
open System
let rotate (arr:_[]) start =
[|start..arr.Length + start - 1|] |> Array.map (fun i -> arr.[i% arr.Length])
//http://stackoverflow.com/questions/833180/handy-f-snippets/851449#851449
let memoize f =
let cache = Collections.Generic.Dictionary<_,_>(HashIdentity.Structural)
fun x ->
match cache.TryGetValue(x) with
| true, res -> res
| _ -> let res = f x
cache.[x] <- res
res
Note
类型封装了一个音符,包括它的名字,符号(NoteSign
),以及它相对于其他音符的位置。但除此之外别无他法。 Aux
模块包含一些用于构造和验证Note
的基础数据结构(请注意,我不太了解这个模块,我宁愿在Note类型上使用私有静态字段,但是F#不支持私有静态字段。由于我使用命名空间而不是模块来保存我的类型(因此我可以使用文件顶部的声明),我不能使用自由浮动let绑定)。我认为提取NoteSign
的模式匹配特别简洁。
namespace MusicTheory
open Utils
open System
///want to use public static field on Note, but don't exist
module Aux =
let indexedNoteNames =
let arr = [|
["B#"; "C"] //flip this order?
["C#";"Db"]
["D"]
["D#";"Eb"]
["E";"Fb"]
["E#";"F" ] //flip this order?
["F#";"Gb"]
["G"]
["G#";"Ab"]
["A"]
["A#";"Bb"]
["B";"Cb"]
|]
Array.AsReadOnly(arr)
let noteNames = indexedNoteNames |> Seq.concat |> Seq.toList
let indexedSignlessNoteNames = [|'A';'B';'C';'D';'E';'F';'G'|]
open Aux
type NoteSign =
| Flat
| Sharp
| Natural
//Represents a note name and it's relative position (index)
type Note(name:string) =
let name =
match noteNames |> List.exists ((=) name) with
| true -> name
| false -> failwith "invalid note name: %s" name
let sign =
match name |> Seq.toArray with
| [|_|] -> NoteSign.Natural
| [|_;'#'|] -> NoteSign.Sharp
| [|_;'b'|] -> NoteSign.Flat
| _ -> failwith "invalid note name sign" //not possible
let index =
indexedNoteNames
|> Seq.findIndex (fun names -> names |> List.exists ((=) name))
with
member self.Name = name
member self.SignlessName = name.[0]
member self.Sign = sign
member self.Index = index
override self.ToString() = name
override self.GetHashCode() = name.GetHashCode()
override self.Equals(other:obj) =
match other with
| :? Note as otherNote -> otherNote.Name = self.Name
| _ -> false
///memoized instances of Note
static member get = memoize (fun name -> Note(name))
接下来是Pitch
,其相对于某个起始点0(C)封装了半音阶中的特定频率。它公开了它所放置的八度音程的计算以及可能描述它的Note
的集合(注意在特定Note
开始的音阶的上下文之外,同样有效)。
namespace MusicTheory
open Utils
open Aux
open System
///A note is a value 0-11 corresponding to positions in the chromatic scale.
///A pitch is any value relative to a starting point of the chromatic scale
type Pitch (pitchIndex:int) =
let pitchIndex = pitchIndex
let noteIndex = Math.Abs(pitchIndex % 12)
let octave =
if pitchIndex >= 0 then (pitchIndex / 12) + 1
else (pitchIndex / 12) - 1
let notes = indexedNoteNames.[noteIndex] |> List.map Note.get
with
member self.Notes = notes
member self.PitchIndex = pitchIndex
member self.NoteIndex = noteIndex
///e.g. pitchIndex = 5 -> 1, pitchIndex = -5 -> -1, pitchIndex = 13 -> 2
member self.Octave = octave
override self.ToString() = sprintf "Notes = %A, PitchIndex = %i, NoteIndex = %i, Octave = %i" notes noteIndex pitchIndex octave
override self.GetHashCode() = pitchIndex
override self.Equals(other:obj) =
match other with
| :? Pitch as otherPitch -> otherPitch.PitchIndex = self.PitchIndex
| _ -> false
///memoized instances of Pitch
static member get = memoize (fun index -> Pitch(index))
///get the first octave pitch for the given note
static member getByNote (note:Note) = note.Index |> Pitch.get
///get the first octave pitch for the given note name
static member getByNoteName name = name |> Note.get |> Pitch.getByNote
在预期我们即将推出的Scale
类型时,我们有一个模块ScaleIntervals
,其中填充了描述音阶的音高间隔列表的子模块(请注意,这与基于索引的表示不同)一直在使用)。为了您的利益,请注意Mode.ionian
和Mode.aeolian
分别对应“主要”和“次要”比例。实际上,您可能希望使用一些外部方法在运行时加载缩放间隔。
//could encapsulate as a type, instead of checking in Scale constructors
///define modes by chromatic interval sequence
module MusicTheory.ScaleIntervals
open Utils
module Mode =
let ionian = [|2;2;1;2;2;2;1|] //i.e. "Major"
let dorian = Utils.rotate ionian 1
let phrygian = Utils.rotate ionian 2
let lydian = Utils.rotate ionian 3
let mixolydian = Utils.rotate ionian 4
let aeolian = Utils.rotate ionian 5 //i.e. "Minor
let locrian = Utils.rotate ionian 6
module EqualTone =
let half = [|1;1;1;1;1;1;1;1;1;1;1;1|]
let whole = [|2;2;2;2;2;2|]
module Pentatonic =
let major = [|2;2;3;2;3|]
let minor = Utils.rotate major 4 //not sure
这是我们解决方案的核心。本身,Scale
非常简单,仅包含一系列比例间隔。但是,当在Pitch
或Note
的上下文中查看时,会产生我们的所有结果。我将指出,在Pitch
或Note
的隔离中,Scale
确实具有一个有趣的特征,即它会产生从比例间隔派生的无限序列RelativeIndices
。使用此方法,我们可以从给定Pitche
(Scale
)开始,从Pitch
生成GetPitches
的无限序列。但现在对于最有趣的方法:GetNotePitchTuples
,它产生无限序列的Note
,Pitch
元组,其中Note
被启发式选择(请参阅该方法的注释)了解更多信息)。 Scale还提供了几个重载,可以更轻松地获取Note
序列,包括ToString(string)
重载,它接受string
Note
名称并返回列出第一个string
Note
名称的八度。
namespace MusicTheory
open Utils
open System
///A Scale is a set of intervals within an octave together with a root pitch
type Scale(intervals:seq<int>) =
let intervals =
if intervals |> Seq.sum <> 12 then
failwith "intervals invalid, do not sum to 12"
else
intervals
let relativeIndices =
let infiniteIntervals = Seq.initInfinite (fun _ -> intervals) |> Seq.concat
infiniteIntervals |> Seq.scan (fun pos cur -> pos+cur) 0
with
member self.Intervals = intervals
member self.RelativeIndices = relativeIndices
override self.ToString() = sprintf "%A" intervals
override self.GetHashCode() = intervals.GetHashCode()
override self.Equals(other:obj) =
match other with
| :? Scale as otherScale -> otherScale.Intervals = self.Intervals
| _ -> false
///Infinite sequence of pitches for this scale starting at rootPitch
member self.GetPitches(rootPitch:Pitch) =
relativeIndices
|> Seq.map (fun i -> Pitch.get (rootPitch.PitchIndex + i))
///Infinite sequence of Note, Pitch tuples for this scale starting at rootPitch.
///Notes are selected heuristically: works perfectly for Modes, but needs some work
///for Pentatonic and EqualTone (perhaps introduce some kind of Sign bias or explicit classification).
member self.GetNotePitchTuples(rootNote:Note, rootPitch:Pitch) =
let selectNextNote (prevNote:Note) (curPitch:Pitch) =
//make sure octave note same as root note
if curPitch.Notes |> List.exists ((=) rootNote) then
rootNote
else
//take the note with the least distance (signless name wise) from the root note
//but not if the distance is 0. assumes curPitch.Notes ordered asc in this way.
//also assumes that curPitch.Notes of length 1 or 2.
match curPitch.Notes with
| [single] -> single
| [first;second] when first.SignlessName = prevNote.SignlessName -> second
| [first;_] -> first
self.GetPitches(rootPitch)
|> Seq.scan
(fun prev curPitch ->
match prev with
| None -> Some(rootNote, rootPitch) //first
| Some(prevNote,_) -> Some(selectNextNote prevNote curPitch, curPitch)) //subsequent
None
|> Seq.choose id
member self.GetNotePitchTuples(rootNote:Note) =
self.GetNotePitchTuples(rootNote, Pitch.getByNote rootNote)
member self.GetNotePitchTuples(rootNoteName:string) =
self.GetNotePitchTuples(Note.get rootNoteName)
///return a string representation of the notes of this scale in an octave for the given note
member self.ToString(note:Note) =
let notes =
(Scale(intervals).GetNotePitchTuples(note))
|> Seq.take (Seq.length intervals + 1)
|> Seq.toList
|> List.map (fst)
sprintf "%A" notes
///return a string representation of the notes of this scale in an octave for the given noteName
member self.ToString(noteName:string) =
self.ToString(Note.get noteName)
这是一个演示:
open MusicTheory
open Aux
open ScaleIntervals
let testScaleNoteHeuristics intervals =
let printNotes (noteName:string) =
printfn "%A" (Scale(intervals).ToString(noteName))
noteNames
|> Seq.iter printNotes
//> testScaleNoteHeuristics Mode.ionian;;
//"[B#; D; E; F; G; A; B; B#]"
//"[C; D; E; F; G; A; B; C]"
//"[C#; D#; E#; F#; G#; A#; B#; C#]"
//"[Db; Eb; F; Gb; Ab; Bb; C; Db]"
//"[D; E; F#; G; A; B; C#; D]"
//"[D#; E#; G; Ab; Bb; C; D; D#]"
//"[Eb; F; G; Ab; Bb; C; D; Eb]"
//"[E; F#; G#; A; B; C#; D#; E]"
//"[Fb; Gb; Ab; A; B; C#; D#; Fb]"
//"[E#; G; A; Bb; C; D; E; E#]"
//"[F; G; A; Bb; C; D; E; F]"
//"[F#; G#; A#; B; C#; D#; E#; F#]"
//"[Gb; Ab; Bb; Cb; Db; Eb; F; Gb]"
//"[G; A; B; C; D; E; F#; G]"
//"[G#; A#; B#; C#; D#; E#; G; G#]"
//"[Ab; Bb; C; Db; Eb; F; G; Ab]"
//"[A; B; C#; D; E; F#; G#; A]"
//"[A#; B#; D; Eb; F; G; A; A#]"
//"[Bb; C; D; Eb; F; G; A; Bb]"
//"[B; C#; D#; E; F#; G#; A#; B]"
//"[Cb; Db; Eb; Fb; Gb; Ab; Bb; Cb]"
//val it : unit = ()
下一步是支持和弦的概念,既与Scale
(一组Pitche
s)隔离,又在Scale
与给定根Note
的上下文中{ {1}}。我没有过多考虑是否有必要进行封装,但是将Scale
增强到(例如)返回和弦的进度(例如每个Note
列表是非常简单的。给定起始Note
和和弦模式(例如三元组)的比例Note
。
答案 3 :(得分:1)
您尝试生成的确切输出尚不清楚。但是,让我们记住一个比例是什么样的:
T T S T T T S
C D E F G A B C
(其中T表示音符之间有两个半音,S表示一个半音。)
知道,按比例生成每个音符都很简单。
一旦你有一个音阶,你可以拉出1-3-5,然后2-4-6等,以获得所有的和弦。
编辑:音阶中有一定数量的音符,您希望能够通过索引获取音符。只需使用数组。答案 4 :(得分:0)
我只想使用一个整数,其中0是键盘上的最低键。每个增量代表半音更高。然后将和弦分解成间隔,例如:
type 1 3 5 7
----------------
maj = 0 4 3 4
min = 0 3 4 3
dom = 0 4 3 3
dim = 0 3 3 3
...
然后你可以做简单的加法来获得和弦的所有音符。因此,从音符43开始,主导和弦将是音符:
43 47 50 53
答案 5 :(得分:0)
我想,取决于你想用八度音调信息做什么。但是,如果你想从八度音符中的音符组中提取特定音符,并且在设置八度音阶后你不打算添加音符,我认为最好使用一个能给你带来好处的音乐随机访问,如Array
。
答案 6 :(得分:0)
在某些时候,您可能需要MIDI音符到频率转换的公式。 IIRC中间C是MIDI音符60,每个积分步长代表半音。这是一些代码:
答案 7 :(得分:0)
您要使用音高类集表示法的和弦。这意味着我们不在乎音符的功能或名称,而只在乎与每个和弦的音调有关的音高等级。
算法
static IEnumerable<IEnumerable<int>> GetChords(int[] scale, int extension)
{
foreach (var degree in Enumerable.Range(0, scale.Length))
{
yield return GetChord(scale, extension, degree);
}
}
static IEnumerable<int> GetChord(int[] scale, int extension, int degree)
{
var d = degree;
var m = extension - 1;
var k = 2;
do
{
yield return scale[d];
d += k;
d %= scale.Length;
m -= k;
} while (m >= 0);
}
验证
static void Main(string[] args)
{
var major = new[] { 0, 2, 4, 5, 7, 8, 11 };
var text = new StringBuilder();
text.AppendLine(Print(GetChords(major, 5))); // triads
text.AppendLine(Print(GetChords(major, 7))); // 7ths
text.AppendLine(Print(GetChords(major, 9))); // 9ths
text.AppendLine(Print(GetChords(major, 11))); // 11ths
text.AppendLine(Print(GetChords(major, 13))); // 13ths
var rendered = text.ToString();
Console.WriteLine(rendered);
Console.ReadKey();
}
static string Print(IEnumerable<IEnumerable<int>> chords)
{
return string.Join(",", chords.Select(chord => string.Join(" ", chord.Select(x => x))));
}
屈服
- 0 4 7,2 5 8,4 7 11,5 8 0,7 11 2,8 0 4,11 2 5
- 0 4 7 11,2 5 8 0,4 7 11 2,5 8 0 4,7 11 2 5,8 0 4 7,11 2 5 8
- 0 4 7 11 2,2 5 8 0 4,4 7 11 2 5,5 8 0 4 7,7 11 2 5 8,8 0 4 7 11,11 2 5 8 0
- 0 4 7 11 2 5,2 5 8 0 4 7,4 7 11 2 5 8,5 8 0 4 7 11,7 11 2 5 8 0,8 0 4 7 11 2,11 2 5 8 0 4
- 0 4 7 11 2 5 8,2 5 8 0 4 7 11,4 7 11 2 5 8 0,5 8 0 4 7 11 2,7 11 2 5 8 0 4,8 0 4 7 11 2 5,11 2 5 8 0 4 7