文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

聊聊使用Rust制作MIDI钢琴程序,你学会了吗?

2024-11-29 22:49

关注

首先,使用以下命令创建一个Rust新项目:

cargo new midi-rs

然后在Cargo.toml文件中加入依赖项:

[dependencies]
eframe = "0.27.2"
itertools = "0.12.1"
phf = { version = "0.11", features = ["macros"] }
rustysynth = "1.3.1"
tinyaudio = "0.1.3"

这个应用程序将打开一个接收键盘事件的egui窗口,这些事件被发送到rustysynth库作为midi音符并通过tinyaudio库输出声音。

首先,在src/main.rs文件中引入这些库:

use eframe::egui;
use itertools::Itertools;
use phf::{phf_map, Map};
use rustysynth::{SoundFont, Synthesizer, SynthesizerSettings};
use std::{
    fs::File,
    sync::{Arc, Mutex},
};
use tinyaudio::prelude::*;

接下来,定义静态变量和常量:

const OUTPUT_PARAMS: OutputDeviceParameters = OutputDeviceParameters {
    channels_count: 2,
    sample_rate: 44100,
    channel_sample_count: 441, // 样本的最大长度
};

#[derive(Debug)]
pub struct MidiNote {
    pub note: i32,
    pub velocity: i32,
}

pub static NOTE_KEY_MAP: Map<&'static str, MidiNote> = phf_map! {
    "A" => MidiNote {
        note: 60,
        velocity: 100,
    },
    "S" => MidiNote {
        note: 62,
        velocity: 100,
    },
    "D" => MidiNote {
        note: 64,
        velocity: 100,
    },
    "F" => MidiNote {
        note: 65,
        velocity: 100,
    },
    "G" => MidiNote {
        note: 67,
        velocity: 100,
    },
};

OUTPUT_PARAMS是tinyaudio的参数。MidiNote持有MIDI音符的音符数和速度,用rustysynth播放它。它们被保存在一个静态映射中,使用一个由按键值索引的phf_map!宏。

让我们定义SynthApp结构体,它是一个egui应用程序。它有合成器对象和方法来执行音符的开/关,在eframe::App的update方法中处理键盘事件。

struct SynthApp {
    synthesizer: Arc>,
    midi_channel: i32,
}

impl SynthApp {
    fn note_on(&mut self, key: &str) {
        let note = match NOTE_KEY_MAP.get(key) {
            Some(note) => note,
            None => return,
        };
        self.synthesizer
            .lock()
            .unwrap()
            .note_on(self.midi_channel, note.note, note.velocity)
    }

    fn note_off(&mut self, key: &str) {
        let note = match NOTE_KEY_MAP.get(key) {
            Some(note) => note,
            None => return,
        };
        self.synthesizer
            .lock()
            .unwrap()
            .note_off(self.midi_channel, note.note);
    }
}

impl eframe::App for SynthApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        ctx.input(|i| {
            for key_str in NOTE_KEY_MAP.keys() {
                if let Some(key) = egui::Key::from_name(key_str) {
                    if i.key_pressed(key) {
                        self.note_on(key_str);
                    } else if i.key_released(key) {
                        self.note_off(key_str);
                    }
                }
            }
        });

        egui::CentralPanel::default().show(ctx, |ui| {
            ui.heading("My egui Application");
            ui.label(format!("Midi channel {}", self.midi_channel));
        });
    }
}

在互联网上有很多不错的音色库,我们使用TimGM6mb.sf2,

可以在以下地址下载:

https://github.com/craffel/pretty-midi/blob/main/pretty_midi/TimGM6mb.sf2

将下载好的文件放入到项目的根目录下。

最后,我们编写main函数,合成器保存在Arc>中,以便run_output_device和SynthApp都可以访问它。

fn main() -> Result<(), eframe::Error> {
    // 加载音色库
    let mut sf2 = File::open("TimGM6mb.sf2").unwrap();
    let sound_font = Arc::new(SoundFont::new(&mut sf2).unwrap());

    // 创建MIDI文件序列器
    let settings = SynthesizerSettings::new(OUTPUT_PARAMS.sample_rate as i32);
    let synthesizer = Arc::new(Mutex::new(
        Synthesizer::new(&sound_font, &settings).unwrap(),
    ));

    // 运行输出设备
    let synth_c = synthesizer.clone();
    let mut left: Vec = vec![0_f32; OUTPUT_PARAMS.channel_sample_count];
    let mut right: Vec = vec![0_f32; OUTPUT_PARAMS.channel_sample_count];
    let _device = run_output_device(OUTPUT_PARAMS, move |data| {
        synth_c
            .lock()
            .unwrap()
            .render(&mut left[..], &mut right[..]);
        for (i, value) in left.iter().interleave(right.iter()).enumerate() {
            data[i] = *value;
        }
    })
    .unwrap();

    // eframe
    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size([640.0, 480.0]),
        ..Default::default()
    };
    eframe::run_native(
        "My egui App",
        options,
        Box::new(|_cc| {
            Box::new(SynthApp {
                synthesizer,
                midi_channel: 0,
            })
        }),
    )
}

执行cargo run,结果如图:

图片

一旦出现窗口,按键盘的ASDFG键,就会播放音符。

为了进一步探索,你可以通过添加一些UI和乐器来尝试egui和rustysynth的各种功能。

来源:coding到灯火阑珊内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯