定义基本音乐元素

音符

音符由绝对音高与时值构成

1
2
3
4
5
#[derive(Debug, Clone, Copy)]
pub struct Note {
pub pitch: AbsulateNotePitch,
pub duration: NoteDuration,
}

音高

音高分为绝对音高与相对音高。

音程

音程可由半音数描述,12个半音音程为一个八度

八度

八度代码定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

#[derive(Debug, Clone, Copy)]
pub enum Octave {
O1,
O2,
O3,
O4,
O5,
O6,
O7,
O8,
}

impl From<u8> for Octave {
fn from(x: u8) -> Self {
match x {
0 => Octave::O1,
1 => Octave::O2,
2 => Octave::O3,
3 => Octave::O4,
4 => Octave::O5,
5 => Octave::O6,
6 => Octave::O7,
7 => Octave::O8,
_ => panic!("invalid octave"),
}
}
}

绝对音高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#[derive(Debug, Clone, Copy)]
pub enum NoteName {
C,
Cs,
D,
Eb,
E,
F,
Fs,
G,
Gs,
A,
Bb,
B,
}

impl From<u8> for NoteName {
fn from(x: u8) -> Self {
match x {
0 => NoteName::C,
1 => NoteName::Cs,
2 => NoteName::D,
3 => NoteName::Eb,
4 => NoteName::E,
5 => NoteName::F,
6 => NoteName::Fs,
7 => NoteName::G,
8 => NoteName::Gs,
9 => NoteName::A,
10 => NoteName::Bb,
11 => NoteName::B,
_ => panic!("invalid note type"),
}
}
}

impl Into<u8> for NoteName {
fn into(self) -> u8 {
self as u8
}
}

#[derive(Debug, Clone, Copy)]
pub struct AbsulateNotePitch {
pub note_type: NoteName,
pub octave: Octave,
}

impl AbsulateNotePitch {
pub fn new(note_type: NoteName, octave: Octave) -> Self {
AbsulateNotePitch { note_type, octave }
}

pub fn add(self, half_tone: i32) -> AbsulateNotePitch {
// 计算基准音(绝对音高)到C1的音程(单位为一个半音)
let old_dist = self.octave as i32 * 12 + self.note_type as i32;
let new_dist = old_dist + half_tone;
let octave = new_dist / 12;
let note_type = new_dist % 12;
AbsulateNotePitch::new((note_type as u8).into(), (octave as u8).into())
}

pub fn frequency(&self) -> f32 {
// 以A4为基准音,频率为440Hz
let base = AbsulateNotePitch::new(NoteName::A, Octave::O4);
const BASE_FREQ: f32 = 440f32;

// 计算self绝对音程
let d = self.octave as i32 * 12 + self.note_type as i32;
// 计算A4的绝对音程
let base_d = base.octave as i32 * 12 + base.note_type as i32;
// 计算相对音程
let half_tone = d - base_d;

// 计算频率
BASE_FREQ * 2.0f32.powf(half_tone as f32 / 12.0)
}
}


相对音高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#[derive(Debug, Clone, Copy)]
pub enum SolfegeName {
Do,
DoSharp,
Re,
ReSharp,
Mi,
Fa,
FaSharp,
Sol,
SolSharp,
La,
LaSharp,
Si,
}

impl From<u8> for SolfegeName {
fn from(x: u8) -> Self {
match x {
0 => SolfegeName::Do,
1 => SolfegeName::DoSharp,
2 => SolfegeName::Re,
3 => SolfegeName::ReSharp,
4 => SolfegeName::Mi,
5 => SolfegeName::Fa,
6 => SolfegeName::FaSharp,
7 => SolfegeName::Sol,
8 => SolfegeName::SolSharp,
9 => SolfegeName::La,
10 => SolfegeName::LaSharp,
11 => SolfegeName::Si,
_ => panic!("invalid note name"),
}
}
}

impl From<SolfegeName> for u8 {
fn from(val: SolfegeName) -> Self {
val as u8
}
}

impl Into<u8> for SolfegeName {
fn into(self) -> u8 {
self as u8
}
}


#[derive(Debug, Clone, Copy)]
pub struct RelativePitch {
pub solfege_name: SolfegeName,
pub octave: Octave,
}

impl RelativePitch {
pub fn new(solfege_name: SolfegeName, octave: Octave) -> Self {
RelativePitch {
solfege_name,
octave,
}
}

/// 给定一个基准音,计算出对应的绝对音高
pub fn to_absulate(self, base: AbsulateNotePitch) -> AbsulateNotePitch {
// 计算相对音高到基准音的音程(单位为一个半音)
let d = self.octave as i32 * 12 + self.solfege_name as i32;
base.add(d)
}
}

时值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

#[derive(Debug, Clone, Copy)]
pub enum NoteDuration {
/// 附点全音符
WholeDotted,
/// 全音符
Whole,
/// 附点二分音符
HalfDotted,
/// 二分音符
Half,
/// 附点四分音符
QuarterDotted,
/// 四分音符
Quarter,
/// 附点八分音符
EighthDotted,
/// 八分音符
Eighth,
/// 附点十六分音符
SixteenthDotted,
/// 十六分音符
Sixteenth,
/// 附点三十二分音符
ThirtySecondDotted,
/// 三十二分音符
ThirtySecond,
/// 附点六十四分音符
SixtyFourthDotted,
/// 六十四分音符
SixtyFourth,
}

impl From<NoteDuration> for f32 {
fn from(val: NoteDuration) -> Self {
match val {
NoteDuration::WholeDotted => 3.0 / 2.0,
NoteDuration::Whole => 1.0,
NoteDuration::HalfDotted => 3.0 / 4.0,
NoteDuration::Half => 1.0 / 2.0,
NoteDuration::QuarterDotted => 3.0 / 8.0,
NoteDuration::Quarter => 1.0 / 4.0,
NoteDuration::EighthDotted => 3.0 / 16.0,
NoteDuration::Eighth => 1.0 / 8.0,
NoteDuration::SixteenthDotted => 3.0 / 32.0,
NoteDuration::Sixteenth => 1.0 / 16.0,
NoteDuration::ThirtySecondDotted => 3.0 / 64.0,
NoteDuration::ThirtySecond => 1.0 / 32.0,
NoteDuration::SixtyFourthDotted => 3.0 / 128.0,
NoteDuration::SixtyFourth => 1.0 / 64.0,
}
}
}

休止符

1
2
3
4
5
6

#[derive(Debug, Clone, Copy)]
pub struct Rest {
pub duration: NoteDuration,
}

定义演奏器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
pub struct Player {
beat_duration: Duration,
_stream: rodio::OutputStream,
sink: rodio::Sink,
}

impl Player {
pub fn new(beat_duration: Duration) -> Self {
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&stream_handle).unwrap();
Player {
beat_duration,
_stream,
sink,
}
}

pub fn from_bpm(bpm: u32) -> Self {
Player::new(Duration::from_secs_f64(60.0 / bpm as f64) * 4)
}

pub fn play_note(&self, note: Note) {
let t = self.beat_duration.mul_f32(note.duration.into());
let s = SineWave::new(note.pitch.frequency()).take_duration(t);
self.sink.append(s);
self.sink.sleep_until_end();
}

pub fn play_rest(&self, rest: Rest) {
thread::sleep(self.beat_duration.mul_f32(rest.duration.into()));
}
}

演奏示例

定义吉他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/// 一根琴弦上的发音
pub struct GuitarNotePitch {
base: AbsulateNotePitch,
position: u8,
}

impl GuitarNotePitch {
pub fn new(base: AbsulateNotePitch, position: u8) -> Self {
GuitarNotePitch { base, position }
}

fn to_absulate(&self) -> AbsulateNotePitch {
let note_type = (self.base.note_type as u8 + self.position) % 12;
let octave = self.base.octave as u8 + (self.base.note_type as u8 + self.position) / 12;
AbsulateNotePitch::new(note_type.into(), octave.into())
}
}

pub struct GuitarNote {
pitch: GuitarNotePitch,
duration: NoteDuration,
}

impl GuitarNote {
pub fn to_absulate(&self) -> Note {
Note {
pitch: self.pitch.to_absulate(),
duration: self.duration,
}
}
}

pub struct Guitar {
base: HashMap<GuitarString, AbsulateNotePitch>,
capo_position: u8,
}

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum GuitarString {
S1,
S2,
S3,
S4,
S5,
S6,
}

impl Default for Guitar {
fn default() -> Self {
use NoteName::*;
use Octave::*;
Self {
base: HashMap::from_iter(vec![
(GuitarString::S1, AbsulateNotePitch::new(E, O4)),
(GuitarString::S2, AbsulateNotePitch::new(B, O3)),
(GuitarString::S3, AbsulateNotePitch::new(G, O3)),
(GuitarString::S4, AbsulateNotePitch::new(D, O3)),
(GuitarString::S5, AbsulateNotePitch::new(A, O2)),
(GuitarString::S6, AbsulateNotePitch::new(E, O2)),
]),
capo_position: 0,
}
}
}

impl Guitar {
pub fn set_capo_position(&mut self, position: u8) {
self.capo_position = position;
}

pub fn get_capo_position(&self) -> u8 {
self.capo_position
}

pub fn to_absulate_note(&self, s: GuitarString, position: u8, duration: NoteDuration) -> Note {
let mut note = GuitarNote {
pitch: GuitarNotePitch::new(self.base[&s], position),
duration,
}
.to_absulate();
note.pitch.add(self.capo_position as i32);
note
}
}

演奏吉他