#はじめに
この記事はこちらの記事の続きです。
#プラグインを作る(CUI)
予備知識からAPI作成を経て、ようやっと本題に入ります。
今回の最終目標はGUIをもったプラグインの作成ですが、まずは簡単なCUIでプラグインの心臓部分を実装します。
###仕様
まずどんなプラグインを作りたいのかを考えましょう。
先のexampleのような半音上げプラグインもいいですが、もう少し実用的なものにしたいです。
実装が難しすぎず、それでいて単調過ぎない機能がいいですね。
とすると、ノートを指定したスケール(音階)にスナップさせるとかいいかもしれません。
そんなこんなで、プロジェクト名はeven_scale
とすることにします。
###実装
では、考えた仕様に沿って関数を実装します。
今後のことも考えてmain()
直書きではなくeven_scale.rs
というファイルにeven_scale()
という関数を実装しましょう。
そしてできたものがこちら。
src/main.rs
mod even_scale;
use std::process;
use utau_rs::*;
fn main(){
let mut uta_sections=UtaSections::default();
let selected_scale=even_scale::Scale::C;
if let Err(err)=even_scale::even_scale(&mut uta_sections,selected_scale){
eprint!("Error:{}\n",err);
process::exit(1);
}
if let Err(err)=uta_sections.write(){
eprint!("Error:{}\n",err);
process::exit(1);
}
}
src/even_scale.rs
use utau_rs::*;
#[allow(non_camel_case_types)]
pub enum Scale{
C,C_sharp,Cm,Cm_sharp,
D,D_sharp,Dm,Dm_sharp,
E,E_sharp,Em,Em_sharp,
F,F_sharp,Fm,Fm_sharp,
G,G_sharp,Gm,Gm_sharp,
A,A_sharp,Am,Am_sharp,
B,B_sharp,Bm,Bm_sharp,
}
pub struct KeyTone([u32;7]);
const C: u32=24;
const D: u32=26;
const E: u32=28;
const F: u32=29;
const G: u32=31;
const A: u32=33;
const B: u32=35;
impl KeyTone{
pub fn new(scale: &Scale)->Result<KeyTone,&'static str>{
Ok(match scale{
Scale::C |Scale::Am =>KeyTone([C,D,E,F,G,A,B]),
Scale::C_sharp|Scale::Am_sharp=>KeyTone([C+1,D+1,E+1,F+1,G+1,A+1,B+1]),
Scale::D |Scale::Bm =>KeyTone([C+1,D,E,F+1,G,A,B]),
Scale::D_sharp|Scale::Cm =>KeyTone([C,D,E-1,F,G,A-1,B-1]),
Scale::E |Scale::Cm_sharp=>KeyTone([C+1,D+1,E,F+1,G+1,A,B]),
Scale::F |Scale::Dm =>KeyTone([C,D,E,F,G,A,B-1]),
Scale::F_sharp|Scale::Dm_sharp=>KeyTone([C+1,D+1,E+1,F+1,G+1,A+1,B]),
Scale::G |Scale::Em =>KeyTone([C,D,E,F+1,G,A,B]),
Scale::G_sharp|Scale::Fm =>KeyTone([C,D-1,E-1,F,G,A-1,B-1]),
Scale::A |Scale::Fm_sharp=>KeyTone([C+1,D,E,F+1,G+1,A,B]),
Scale::A_sharp|Scale::Gm =>KeyTone([C,D,E-1,F,G,A,B-1]),
Scale::B |Scale::Gm_sharp=>KeyTone([C+1,D+1,E,F+1,G+1,A+1,B]),
_=>return Err("不明なエラーが発生しました."),
})
}
fn unwrap(&self)->[u32;7]{
match self{
&KeyTone(some)=>some,
}
}
}
pub fn even_scale(uta_sections: &mut UtaSections,scale: Scale)->Result<(),&'static str>{
let tones=KeyTone::new(&scale).unwrap();
for section in uta_sections.sections.iter_mut(){
if tones.unwrap().iter().all(|&x|(x%section.note_num)!=0){
let mut near=section.note_num as i32-tones.unwrap()[0] as i32;
for tone in tones.unwrap(){
let tone=match section.note_num{
24..=35=>tone+12*0,
36..=47=>tone+12*1,
48..=59=>tone+12*2,
60..=71=>tone+12*3,
72..=83=>tone+12*4,
84..=95=>tone+12*5,
96..=107=>tone+12*6,
_=>return Err("不明なエラーが発生しました."),
};
if (section.note_num as i32-tone as i32).abs()<near{
near=(section.note_num as i32-tone as i32).abs();
}
}
section.note_num=(section.note_num as i32+near) as u32;
}
};
Ok(())
}
そこそこ長くなりましたね。
さて、ではコードについて解説しましょうと言いたいところですが、全てのコードに対して解説をしようとなるとなかなか骨が折れるので、要所要所で解説をします。
mod even_scale;
even_scale.rs
をモジュールとし、諸々のオブジェクト等々をスコープへ持ち込みます。
use
のようにも思えますが、lib.rs
以外のバイナリファイルをmain.rs
に持ち込む際にはmod
の方を使うものだと勝手に思っています。
ここら辺まだ理解が怪しいです。
let selected_scale=even_scale::Scale::C;
後述のScale
列挙体のScale::C
を選択したスケールとします。
本当は標準入力に応じてselected_scale
の値を変えた方がいいとは思うのですが、実装が面倒でした。
#[allow(non_camel_case_types)]
pub enum Scale{/*省略*/}
Scale
列挙体をnon_camel_case_types
で定義します。
Rustのenumは基本的に、以下のようなcamel_case
という命名規則で定義するのが好ましいとされています。
enum Fruits{
Apple,
Orange,
ApplePear,
}
ですがScale
ではC_sharp
などcamel_case
に則っていない変数が定義されているため、この#[allow(non_camel_case_types)]
を使用しないとコンパイラが怒ります。
プログラムに影響するわけではないのですが、警告がうざったいという方は面倒ですが使いましょう。
pub fn even_scale(uta_sections: &mut UtaSections,scale: Scale)
->Result<(),&'static str>{/*省略*/}
Scale
列挙体によって指定されたスケールによってuta_sections.section.note_num
の値を変更します。
ポイントは以下の範囲マッチングです。
let tone=match section.note_num{
24..=35=>tone+12*0, //C1~B1
36..=47=>tone+12*1, //C2~B2
48..=59=>tone+12*2, //C3~B3
60..=71=>tone+12*3, //C4~B4
72..=83=>tone+12*4, //C5~B5
84..=95=>tone+12*5, //C6~B6
96..=107=>tone+12*6, //C7~B7
_=>return Err("不明なエラーが発生しました."),
};
ここでnote_num
のオクターヴを判別し、これ以下で値の変更等々を行います。
Cのswitch
とは違い、パターンマッチングは本当に強力な言語機能だと改めて思いました。
###テスト
では実際に動くかどうかテストをしましょう。
今回の実装ではselected_scale
はeven_scale::Scale::C
ですので、スナップするスケールはハ長調(C,D,E,F,G,A,B)であるはずです。
大丈夫そうですね。
ちなみに少し話がそれてしまうのですが、テストに使用している音源は「霜月奏凪」という連続音源で、私が自作したものです。
以下のサイトからDLやデモソングの閲覧が可能ですので、興味があればぜひそちらもご覧ください。
#おわりに
今回はプラグインの仕様の決定と簡単なCUIによる実装を行いました。
プラグインを実行した瞬間に実行が完了するのでUIも何もないですが...。
次回は今回作成したCUIをGUIに拡張しようと思います。
またね。