LoginSignup
2
0

More than 1 year has passed since last update.

【Rust】UTAUプラグインを作ってみる話。(2/3) ~CUI作成編~

Last updated at Posted at 2022-01-06

はじめに

この記事はこちらの記事の続きです。

プラグインを作る(CUI)

予備知識からAPI作成を経て、ようやっと本題に入ります。
今回の最終目標はGUIをもったプラグインの作成ですが、まずは簡単なCUIでプラグインの心臓部分を実装します。

仕様

まずどんなプラグインを作りたいのかを考えましょう。
先のexampleのような半音上げプラグインもいいですが、もう少し実用的なものにしたいです。
実装が難しすぎず、それでいて単調過ぎない機能がいいですね。
とすると、ノートを指定したスケール(音階)にスナップさせるとかいいかもしれません。
そんなこんなで、プロジェクト名はeven_scaleとすることにします。

実装

では、考えた仕様に沿って関数を実装します。
今後のことも考えてmain()直書きではなくeven_scale.rsというファイルにeven_scale()という関数を実装しましょう。
そしてできたものがこちら。

src/main.rs
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
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(())
}

そこそこ長くなりましたね。
さて、ではコードについて解説しましょうと言いたいところですが、全てのコードに対して解説をしようとなるとなかなか骨が折れるので、要所要所で解説をします。


src/main.rs
mod even_scale;

even_scale.rsをモジュールとし、諸々のオブジェクト等々をスコープへ持ち込みます。
useのようにも思えますが、lib.rs以外のバイナリファイルをmain.rsに持ち込む際にはmodの方を使うものだと勝手に思っています。
ここら辺まだ理解が怪しいです。


src/main.rs
let selected_scale=even_scale::Scale::C;

後述のScale列挙体のScale::Cを選択したスケールとします。
本当は標準入力に応じてselected_scaleの値を変えた方がいいとは思うのですが、実装が面倒でした。


src/even_scale.rs
#[allow(non_camel_case_types)]
pub enum Scale{/*省略*/}

Scale列挙体をnon_camel_case_typesで定義します。
Rustのenumは基本的に、以下のようなcamel_caseという命名規則で定義するのが好ましいとされています。

camel_caseなenum
enum Fruits{
    Apple,
    Orange,
    ApplePear,
}

ですがScaleではC_sharpなどcamel_caseに則っていない変数が定義されているため、この#[allow(non_camel_case_types)]を使用しないとコンパイラが怒ります。
プログラムに影響するわけではないのですが、警告がうざったいという方は面倒ですが使いましょう。


even_scale.rs
pub fn even_scale(uta_sections: &mut UtaSections,scale: Scale)
    ->Result<(),&'static str>{/*省略*/}

Scale列挙体によって指定されたスケールによってuta_sections.section.note_numの値を変更します。
ポイントは以下の範囲マッチングです。

src/even_scale.rs
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_scaleeven_scale::Scale::Cですので、スナップするスケールはハ長調(C,D,E,F,G,A,B)であるはずです。

Before
before
After
after

大丈夫そうですね。
ちなみに少し話がそれてしまうのですが、テストに使用している音源は「霜月奏凪」という連続音源で、私が自作したものです。
以下のサイトからDLやデモソングの閲覧が可能ですので、興味があればぜひそちらもご覧ください。

おわりに

今回はプラグインの仕様の決定と簡単なCUIによる実装を行いました。
プラグインを実行した瞬間に実行が完了するのでUIも何もないですが...。
次回は今回作成したCUIをGUIに拡張しようと思います。

またね。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0