Scale Generator

1. Readme

音阶生成器

译:真不知道说什么。

给出一个音调,或是开始的音符以及一组间隔,从音调开始并遵循指定的间隔模式生成音阶。

西方音乐中的音阶,基于彩色(12 音符)音阶。该音阶可表示为以下一组音高:

A,A#,B,C,C#,D,D#,E,F,F#,G,G#

给出一个尖音符(用#表示)也可以表示为它前面的平音符(用 b 表示),所以半音音阶也可以这样写:

A,Bb,B,C,Db,D,Eb,E,F,Gb,G,Ab

大调和小调的音阶和模式是这个十二音高集合的子集。它们有七个音高,称为全音阶音阶。这些音阶中的音符集合使用锐利或平面,根据音调。这是一个列表,其中包括:

没有尖(Sharps)或平(Flats):C 大调(major)和 a 小调(minor)

使用 Sharps: G, D, A, E, B, F# major e, b, f#, c#, g#, d# minor

使用 Flats: F, Bb, Eb, Ab, Db, Gb major d, g, c, f, bb, eb minor

全音阶音阶以及源自半音音阶的所有其他音阶都是间隔建立的。间隔是两个音高之间的间距。

最简单的间隔是在两个相邻音符之间,称为”半步”或”小秒”(有时写为小写”m”)。具有中间音符的两个音符之间的间隔称为”整步”或”大秒”(写为大写”M”)。仅使用相邻音符之间的这两个间隔,来构建全音阶音阶。

非全音阶音阶可以包含其他间隔。一个”先增强”的间隔,写上 A,具有两个中间音符(例如,从 A 到 C 或从 D 到 E)。间隔也越来越小,但他们不会参与这个练习。

2. 开始你的表演

// You should change this.
//
// Depending on your implementation, there are a variety of potential errors
// which might occur. They aren't checked by the test suite in order to
// allow the greatest freedom of implementation, but real libraries should
// provide useful, descriptive errors so that downstream code can react
// appropriately.
//
// One common idiom is to define an Error enum which wraps all potential
// errors. Another common idiom is to use a helper type such as failure::Error
// which does more or less the same thing but automatically.
pub type Error = ();

pub struct Scale;

impl Scale {
   pub fn new(tonic: &str, intervals: &str) -> Result<Scale, Error> {
       unimplemented!(
           "Construct a new scale with tonic {} and intervals {}",
           tonic,
           intervals
       )
   }

   pub fn chromatic(tonic: &str) -> Result<Scale, Error> {
       unimplemented!("Construct a new chromatic scale with tonic {}", tonic)
   }

   pub fn enumerate(&self) -> Vec<String> {
       unimplemented!()
   }
}

3. 测试代码查看


# #![allow(unused_variables)]
#fn main() {
/// Tests for scale-generator
///
/// Generated by [script][script] using [canonical data][canonical-data]
///
/// [script]: https://github.com/exercism/rust/blob/master/bin/init_exercise.py
/// [canonical-data]: https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/scale-generator/canonical_data.json

/// Process a single test case for the property `chromatic`
///
/// All cases for the `chromatic` property are implemented
/// in terms of this function.
fn process_chromatic_case(tonic: &str, expected: &[&str]) {
   let s = Scale::chromatic(tonic).unwrap();
   assert_eq!(s.enumerate(), expected);
}

/// Process a single test case for the property `interval`
///
/// All cases for the `interval` property are implemented
/// in terms of this function.
fn process_interval_case(tonic: &str, intervals: &str, expected: &[&str]) {
   let s = Scale::new(tonic, intervals).unwrap();
   assert_eq!(s.enumerate(), expected);
}

// Chromatic scales
// These tests have no interval.
// The chromatic scale is considered the default scale

#[test]
/// Chromatic scale with sharps
fn test_chromatic_scale_with_sharps() {
   process_chromatic_case(
       "C",
       &[
           "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
       ],
   );
}

#[test]
//#[ignore]
/// Chromatic scale with flats
fn test_chromatic_scale_with_flats() {
   process_chromatic_case(
       "F",
       &[
           "F", "Gb", "G", "Ab", "A", "Bb", "B", "C", "Db", "D", "Eb", "E",
       ],
   );
}

// Scales with specified intervals
// These tests all have intervals and are explorations of different
// traversals of the scale.

#[test]
//#[ignore]
/// Simple major scale
///
/// The simplest major scale, with no sharps or flats.
fn test_simple_major_scale() {
   process_interval_case("C", "MMmMMMm", &["C", "D", "E", "F", "G", "A", "B"]);
}

#[test]
//#[ignore]
/// Major scale with sharps
fn test_major_scale_with_sharps() {
   process_interval_case("G", "MMmMMMm", &["G", "A", "B", "C", "D", "E", "F#"]);
}

#[test]
//#[ignore]
/// Major scale with flats
fn test_major_scale_with_flats() {
   process_interval_case("F", "MMmMMMm", &["F", "G", "A", "Bb", "C", "D", "E"]);
}

#[test]
//#[ignore]
/// Minor scale with sharps
fn test_minor_scale_with_sharps() {
   process_interval_case("f#", "MmMMmMM", &["F#", "G#", "A", "B", "C#", "D", "E"]);
}

#[test]
//#[ignore]
/// Minor scale with flats
fn test_minor_scale_with_flats() {
   process_interval_case("bb", "MmMMmMM", &["Bb", "C", "Db", "Eb", "F", "Gb", "Ab"]);
}

#[test]
//#[ignore]
/// Dorian mode
fn test_dorian_mode() {
   process_interval_case("d", "MmMMMmM", &["D", "E", "F", "G", "A", "B", "C"]);
}

#[test]
//#[ignore]
/// Mixolydian mode
fn test_mixolydian_mode() {
   process_interval_case("Eb", "MMmMMmM", &["Eb", "F", "G", "Ab", "Bb", "C", "Db"]);
}

#[test]
//#[ignore]
/// Lydian mode
fn test_lydian_mode() {
   process_interval_case("a", "MMMmMMm", &["A", "B", "C#", "D#", "E", "F#", "G#"]);
}

#[test]
//#[ignore]
/// Phrygian mode
fn test_phrygian_mode() {
   process_interval_case("e", "mMMMmMM", &["E", "F", "G", "A", "B", "C", "D"]);
}

#[test]
//#[ignore]
/// Locrian mode
fn test_locrian_mode() {
   process_interval_case("g", "mMMmMMM", &["G", "Ab", "Bb", "C", "Db", "Eb", "F"]);
}

#[test]
//#[ignore]
/// Harmonic minor
///
/// Note that this case introduces the accidental interval (A)
fn test_harmonic_minor() {
   process_interval_case("d", "MmMMmAm", &["D", "E", "F", "G", "A", "Bb", "Db"]);
}

#[test]
//#[ignore]
/// Octatonic
fn test_octatonic() {
   process_interval_case(
       "C",
       "MmMmMmMm",
       &["C", "D", "D#", "F", "F#", "G#", "A", "B"],
   );
}

#[test]
//#[ignore]
/// Hexatonic
fn test_hexatonic() {
   process_interval_case("Db", "MMMMMM", &["Db", "Eb", "F", "G", "A", "B"]);
}

#[test]
//#[ignore]
/// Pentatonic
fn test_pentatonic() {
   process_interval_case("A", "MMAMA", &["A", "B", "C#", "E", "F#"]);
}

#[test]
//#[ignore]
/// Enigmatic
fn test_enigmatic() {
   process_interval_case("G", "mAMMMmm", &["G", "G#", "B", "C#", "D#", "F", "F#"]);
}

#}

4. 答案


# #![allow(unused_variables)]
#fn main() {
#[macro_use]
extern crate enum_primitive_derive;
extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate itertools;
extern crate num_traits;

pub use self::interval::{Interval, Intervals};
use self::note::Accidental;
pub use self::note::Note;
use failure::Error;
use std::str::FromStr;

pub mod interval {
   use itertools::Itertools;
   use std::fmt;
   use std::ops::Deref;
   use std::str::FromStr;

   #[derive(Debug, Clone, Copy, PartialEq, Eq, Fail)]
   pub enum ParseErr {
       #[fail(display = "invalid interval")]
       InvalidInterval,
       #[fail(display = "wrong number of semitones")]
       WrongNumberOfSemitones,
   }

   #[derive(Debug, Clone, Copy, PartialEq, Eq, Primitive)]
   pub enum Interval {
       HalfStep = 1,
       WholeStep = 2,
       AugmentedFirst = 3,
   }

   impl fmt::Display for Interval {
       fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
           use self::Interval::*;
           write!(
               f,
               "{}",
               match self {
                   HalfStep => "m",
                   WholeStep => "M",
                   AugmentedFirst => "A",
               }
           )
       }
   }

   impl FromStr for Interval {
       type Err = ParseErr;

       fn from_str(s: &str) -> Result<Self, Self::Err> {
           use self::Interval::*;
           match s {
               "m" => Ok(HalfStep),
               "M" => Ok(WholeStep),
               "A" => Ok(AugmentedFirst),
               _ => Err(ParseErr::InvalidInterval),
           }
       }
   }

   #[derive(Debug, Clone, PartialEq, Eq)]
   pub struct Intervals(Vec<Interval>);

   impl fmt::Display for Intervals {
       fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
           write!(f, "{}", self.0.iter().join(""))
       }
   }

   impl FromStr for Intervals {
       type Err = ParseErr;

       fn from_str(s: &str) -> Result<Self, Self::Err> {
           let mut semitones = Vec::with_capacity(s.len());

           for (i, c) in s.char_indices() {
               semitones.push(Interval::from_str(&s[i..i + c.len_utf8()])?);
           }

           if semitones.iter().take(12).map(|&i| i as u8).sum::<u8>() == 12 {
               Ok(Intervals(semitones))
           } else {
               Err(ParseErr::WrongNumberOfSemitones)
           }
       }
   }

   impl Deref for Intervals {
       type Target = Vec<Interval>;

       fn deref(&self) -> &Self::Target {
           &self.0
       }
   }

   #[cfg(test)]
   mod test {
       use super::*;

       #[test]
       fn test_parse_chromatic() {
           assert!("mmmmmmmmmmmm".parse::<Intervals>().is_ok());
       }

       #[test]
       fn test_parse_major() {
           assert!("MMmMMMm".parse::<Intervals>().is_ok());
       }

       #[test]
       fn test_parse_minor() {
           assert!("MmMMmMM".parse::<Intervals>().is_ok());
       }
   }
}

pub mod note {
   use interval::Interval;
   use num_traits::{FromPrimitive, ToPrimitive};
   use std::fmt;
   use std::ops::AddAssign;
   use std::str::FromStr;

   pub const SEMITONES: i8 = 12;

   #[derive(Debug, Clone, Copy, PartialEq, Eq, Primitive)]
   pub enum Semitone {
       A = 0,
       ASharp = 1,
       B = 2,
       C = 3,
       CSharp = 4,
       D = 5,
       DSharp = 6,
       E = 7,
       F = 8,
       FSharp = 9,
       G = 10,
       GSharp = 11,
   }

   #[derive(Debug, Clone, Copy, PartialEq, Eq, Primitive)]
   pub enum Root {
       A = 0,
       B = 2,
       C = 3,
       D = 5,
       E = 7,
       F = 8,
       G = 10,
   }

   impl fmt::Display for Root {
       fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
           write!(f, "{:?}", self)
       }
   }

   #[derive(Debug, Clone, Copy, PartialEq, Eq)]
   pub enum Accidental {
       Sharp,
       Flat,
   }

   impl Accidental {
       fn to_i8(&self) -> i8 {
           match *self {
               Accidental::Sharp => 1,
               Accidental::Flat => -1,
           }
       }

       pub fn from_tonic(tonic: &str) -> Accidental {
           match tonic {
               "C" | "a" | "G" | "D" | "A" | "E" | "B" | "F#" | "e" | "b" | "f#" | "c#" | "g#"
               | "d#" => Accidental::Sharp,
               _ => Accidental::Flat,
           }
       }
   }

   impl fmt::Display for Accidental {
       fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
           write!(
               f,
               "{}",
               match &self {
                   Accidental::Sharp => '#',
                   Accidental::Flat => 'b',
               }
           )
       }
   }

   #[derive(Debug, Clone, Copy, PartialEq, Eq)]
   pub struct Note {
       tonic: Root,
       accidental: Option<Accidental>,
   }

   impl Note {
       pub fn canonicalize(&self, lean: Accidental) -> Note {
           let mut n: Note = Semitone::from(*self).into();
           if let Some(accidental) = n.accidental {
               if accidental != lean {
                   if lean == Accidental::Flat {
                       n += Interval::HalfStep;
                       n.accidental = Some(Accidental::Flat);
                   }
               }
           }
           n
       }
   }

   impl fmt::Display for Note {
       fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
           write!(
               f,
               "{}{}",
               self.tonic,
               self.accidental.map_or(String::new(), |a| a.to_string()),
           )
       }
   }

   #[derive(Debug, Clone, Copy, PartialEq, Eq, Fail)]
   pub enum ParseErr {
       #[fail(display = "invalid length")]
       InvalidLength,
       #[fail(display = "invalid tonic")]
       InvalidTonic,
       #[fail(display = "invalid accidental")]
       InvalidAccidental,
   }

   impl FromStr for Note {
       type Err = ParseErr;

       fn from_str(s: &str) -> Result<Self, Self::Err> {
           let lc = s.to_lowercase();
           let mut iter = lc.chars();

           let mut note = match iter.next() {
               Some(c) if 'a' <= c && 'g' >= c => Note {
                   tonic: match c {
                       'a' => Root::A,
                       'b' => Root::B,
                       'c' => Root::C,
                       'd' => Root::D,
                       'e' => Root::E,
                       'f' => Root::F,
                       'g' => Root::G,
                       _ => return Err(ParseErr::InvalidTonic),
                   },
                   accidental: None,
               },
               Some(_) => return Err(ParseErr::InvalidTonic),
               None => return Err(ParseErr::InvalidLength),
           };

           match iter.next() {
               Some('b') => note.accidental = Some(Accidental::Flat),
               Some('#') => note.accidental = Some(Accidental::Sharp),
               Some(_) => return Err(ParseErr::InvalidAccidental),
               None => {}
           }

           if iter.next().is_some() {
               return Err(ParseErr::InvalidLength);
           }

           Ok(note)
       }
   }

   impl From<Semitone> for Note {
       fn from(s: Semitone) -> Self {
           Note {
               tonic: match s {
                   Semitone::A | Semitone::ASharp => Root::A,
                   Semitone::B => Root::B,
                   Semitone::C | Semitone::CSharp => Root::C,
                   Semitone::D | Semitone::DSharp => Root::D,
                   Semitone::E => Root::E,
                   Semitone::F | Semitone::FSharp => Root::F,
                   Semitone::G | Semitone::GSharp => Root::G,
               },
               accidental: match s {
                   Semitone::ASharp
                   | Semitone::CSharp
                   | Semitone::DSharp
                   | Semitone::FSharp
                   | Semitone::GSharp => Some(Accidental::Sharp),
                   _ => None,
               },
           }
       }
   }

   impl From<Note> for Semitone {
       fn from(n: Note) -> Self {
           Semitone::from_i8(
               (SEMITONES + n.tonic.to_i8().unwrap() + n.accidental.map_or(0, |a| a.to_i8()))
                   % SEMITONES,
           ).expect("must have valid semitone")
       }
   }

   impl AddAssign<Interval> for Note {
       fn add_assign(&mut self, rhs: Interval) {
           *self = Semitone::from_i8(
               (SEMITONES + Semitone::from(*self).to_i8().unwrap() + rhs.to_i8().unwrap())
                   % SEMITONES,
           ).unwrap()
               .into();
       }
   }

}

#[derive(Debug)]
pub struct Scale {
   tonic: Note,
   lean: Accidental,
   intervals: Intervals,
}

impl Scale {
   pub fn new(tonic: &str, intervals: &str) -> Result<Scale, Error> {
       Ok(Scale {
           tonic: Note::from_str(tonic)?,
           lean: Accidental::from_tonic(tonic),
           intervals: Intervals::from_str(intervals)?,
       })
   }

   pub fn chromatic(tonic: &str) -> Result<Scale, Error> {
       Scale::new(tonic, "mmmmmmmmmmmm")
   }

   pub fn enumerate(&self) -> Vec<String> {
       let mut out = Vec::with_capacity(self.intervals.len());

       let mut note = self.tonic;
       for &interval in self.intervals.iter() {
           out.push(note.canonicalize(self.lean).to_string());
           note += interval;
       }

       out
   }
}

#}



填充/相关