#reverse-engineering #sdk #midi #audio #low-level #rytm #analogrytm

rytm-rs

rytm-sys 的安全 Rust 抽象,rytm-sys 是 Analog Rytm MKII 运行在 1.70 固件上的非官方 SDK

2 个版本

0.1.1 2023 年 12 月 22 日
0.1.0 2023 年 12 月 15 日

#359编码

MIT 许可证

1MB
22K SLoC

rytm-rs

rytm-rs 在 rytm-sys 的基础上提供了 Rust 的安全抽象,rytm-sys 是用于编写 Analog Rytm 软件的非官方 SDK,该软件运行在 1.70 固件上。

除了 CC 和 NRPN 消息之外,Rytm 还接受 sysex 消息,这些消息未经文档记录,并且没有得到 Elektron 的官方支持。

sysex 格式的逆向工程工作始于 libanalogrytm,这是一个 C 库,通过 rytm-sys 绑定为 rytm-rs 的部分功能提供动力。

libanalogrytm 虽然是一个很好的基础,但由于其低级性质,并且缺乏对常见任务的高级抽象,因此对许多开发者来说并不易于访问。libanalogrytm 的范围是提供编码和解码 sysex 消息所需的必要类型,并关注 sysex 协议的低级细节。

rytm-rs 基于 libanalogrytm 构建,并为常见任务提供高级抽象,旨在为开发者提供类似 SDK 的体验,同时考虑到易用性,并完全抽象了低级细节。

它有详细的文档,可以立即开始使用。

特性

  • Rytm 项目的所有结构都完全由一个嵌套结构 RytmProject 表示,其中包含所有必要的字段和方法,以接收、操作和将项目发送到设备。
  • 所有获取和设置方法都具有范围和有效性检查,包括关于值范围和有效性的注释。
  • Rytm 设备项目的默认值在所有 Default 实现中表示。
  • sysex 编码和解码完全抽象。使用单个方法调用更新项目。
  • 使用一个方法调用将项目的一部分转换为 sysex,并通过您选择的方式将其发送到设备。
  • PatternKitSoundSettingsGlobal类型提供了单独的查询类型,涵盖整个Rytm项目参数,除了歌曲。
  • 提供了不同的方法来设置、获取、清除参数锁定,这些方法在Trig结构体中可用。
  • 包括参数锁定的设置器、获取器和清除器,所有34种机器类型都有表示。
  • 所有获取器和设置器都使用设备上的实际值范围,而不是在sysex协议中使用内部值范围。
  • 提供了将项目序列化和反序列化为JSON的功能。但实际上这是实验性的,我认为它并不有用,因为序列化的项目大约32MB,太大了。

目的

此crate的目的是为希望为Analog Rytm编写软件的开发者提供安全且易于使用的SDK类似体验。

此crate的首要任务是向希望开发Analog Rytm软件的开发者提供易于使用的API。

  • 开发Analog Rytm的软件产品
  • 开发用于艺术目的的定制创意软件
  • 探索和实验生成性和算法音乐,但不想处理与设备通信的sysex协议的底层细节。

此crate没有针对最佳性能或内存进行优化。另一方面,内存占用不大,性能足够好,因为性能瓶颈在于设备本身在sysex通信时。

我相信Rytm在他们的内部RTOS中为sysex通信使用了一个低优先级的线程。如果您向Rytm发送大量sysex消息,它会将响应排队,并在可以时再回复您。这在大多数用例中都不是问题,但这是一个值得知道的信息。

rytm-rs由3个主要层组成。

rytm-sys

  • 编码/解码sysex消息
  • 提供用于在内存中相同地表示sysex消息的#[repr(C,packed)]结构体,以保持消息的原始内存布局。
  • 通过rytm-sys绑定公开来自libanalogrytm的类型。这是逆向工程的主要枢纽。

rytm-rs

处理与rytm-sys通信的内部层,并处理从/到原始类型(#[repr(C,packed)]结构体)的转换。

面向用户的层,为常见任务提供高级抽象。获取器、设置器等。

用法

从导入预处理器开始是一个好主意,因为它将必要的特性和类型引入作用域。

此外,这些示例中将使用midir库来与设备进行midi通信,但您可以使用任何midi库。

use std::sync::{Arc, Mutex};
use midir::{Ignore, MidiInputConnection, MidiOutputConnection};
use rytm_rs::prelude::*;

// We'll be using this connection for sending sysex messages to the device.
//
// Using an Arc<Mutex<MidiOutputConnection>> is a good idea since you can share the connection between threads.
// Which will be common in this context.
fn get_connection_to_rytm() -> Arc<Mutex<MidiOutputConnection>> {
    let output = port::MidiOut::new("rytm_test_out").unwrap();
    let rytm_out_identifier = "Elektron Analog Rytm MKII";
    let rytm_output_port = output.find_output_port(rytm_out_identifier).unwrap();

    Arc::new(Mutex::new(
        output.make_output_connection(&rytm_output_port, 0).unwrap(),
    ))
}

// We'll be using this connection for receiving sysex messages from the device and forwarding them to our main thread.
pub fn make_input_message_forwarder() -> (
    MidiInputConnection<()>,
    std::sync::mpsc::Receiver<(Vec<u8>, u64)>,
) {
    let mut input = crate::port::MidiIn::new("rytm_test_in").unwrap();
    input.ignore(Ignore::None);
    let rytm_in_identifier = "Elektron Analog Rytm MKII";
    let rytm_input_port = input.find_input_port(rytm_in_identifier).unwrap();

    let (tx, rx) = std::sync::mpsc::channel::<(Vec<u8>, u64)>();

    let conn_in: midir::MidiInputConnection<()> = input
        .into_inner()
        .connect(
            &rytm_input_port,
            "rytm_test_in",
            move |stamp, message, _| {
                // Do some filtering here if you like.
                tx.send((message.to_vec(), stamp)).unwrap();
            },
            (),
        )
        .unwrap();

    (conn_in, rx)
}

fn main() {
    // Make a default rytm project
    let mut rytm = RytmProject::default();

    // Get a connection to the device
    let conn_out = get_connection_to_rytm();

    // Listen for incoming messages from the device
    let (_conn_in, rx) = make_input_message_forwarder();

    // Make a query for the pattern in the work buffer
    let query = PatternQuery::new_targeting_work_buffer();

    // Send the query to the device
    conn_out
        .lock()
        .unwrap()
        .send(&query.as_sysex().unwrap())
        .unwrap();

    // Wait for the response
    match rx.recv() {
        Ok((message, _stamp)) => {
            match rytm.update_from_sysex_response(&message) {
                Ok(_) => {
                    for track in rytm.work_buffer_mut().pattern_mut().tracks_mut() {
                        // Set the number of steps to 64
                        track.set_number_of_steps(64).unwrap();
                        for (i, trig) in track.trigs_mut().iter_mut().enumerate() {
                            // Enable every 4th trig.
                            // Set retrig on.
                            if i % 4 == 0 {
                                trig.set_trig_enable(true);
                                trig.set_retrig(true);
                            }
                        }
                    }

                    // Send the updated pattern to the device if you like
                    conn_out
                        .lock()
                        .unwrap()
                        .send(&rytm.work_buffer().pattern().as_sysex().unwrap())
                        .unwrap();
                }
                Err(err) => {
                    println!("Error: {:?}", err);
                }
            }
        }
        Err(err) => {
            println!("Error: {:?}", err);
        }
    }
}

测试

测试目前一团糟。它们不是为了运行而设计的,而是作为逆向工程和手动测试库的游乐场。

我将编写一些自动集成测试,这需要连接到设备。这同样需要手动运行,但可以以更自动化的方式测试库。

贡献

欢迎贡献!

我独自一人完成了这项工作,真是不容易。这中间包含了许多劳动和爱。还有数周单调的逆向工程和手动测试工作。所以我很乐意看到一些贡献。

由于我独自一人,即使我已经彻底测试了库多次,可能仍然存在一些错误。所以如果你发现任何问题,请提出一个issue。我会非常感激。

还有一些想法,可能会在未来对社区很有用。

  • 人们对Rytm的Max/MSP外部扩展感到非常兴奋。你可以在本crate的基础上构建这个外部扩展。查看median
  • Neon绑定可能非常有用,这样人们就可以使用Node for Max轻松地在crate上构建Max补丁或Live设备。
  • 将crate扩展以支持轻松与CCNRPN消息接口的想法。

你还可以在代码库中搜索TODO以找到一些想法。

对于此repo的所有通信和贡献,均适用Rust行为准则

许可

此crate采用MIT许可。你可以基本上用它做任何你想做的事,但如果你能从中获得丰厚的利润或用于重大商业项目,我很乐意你联系我。

备注

这里提到的人是逆向工程工作的主要贡献者,我要感谢他们的工作。没有他们的工作,这个crate不可能以这种形式和在这个时间框架内完成。

bsp2

libanalogrytm的维护者以及逆向工程工作的原始作者。他是开始逆向工程工作并提供初始C库的人,这是rytm-rs的基础。

mekohler

Collider应用的作者,可在App Store中的iPad应用中找到。逆向工程工作的另一位贡献者。

void

STROM应用的作者,可在App Store中的iPad应用中找到。逆向工程工作的另一位贡献者。

依赖

~1.2–4MB
~80K SLoC