{"id":16820975,"url":"https://github.com/kangalio/launchy","last_synced_at":"2025-09-07T17:44:56.203Z","repository":{"id":48279328,"uuid":"273340895","full_name":"kangalio/launchy","owner":"kangalio","description":"An exhaustive Rust API for the Novation Launchpad devices, optimized for maximum expressiveness and minimum boilerplate!","archived":false,"fork":false,"pushed_at":"2025-06-19T14:52:54.000Z","size":198,"stargazers_count":38,"open_issues_count":2,"forks_count":14,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-19T15:42:11.768Z","etag":null,"topics":["launchpad","lightshow","midi","midi-controller","novation"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kangalio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-18T21:11:23.000Z","updated_at":"2025-06-19T14:52:58.000Z","dependencies_parsed_at":"2024-10-13T10:58:45.296Z","dependency_job_id":"a78bdf1b-3f36-4c0f-ac0c-f9f959eb27c9","html_url":"https://github.com/kangalio/launchy","commit_stats":null,"previous_names":["kangalioo/launchy"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/kangalio/launchy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kangalio%2Flaunchy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kangalio%2Flaunchy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kangalio%2Flaunchy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kangalio%2Flaunchy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kangalio","download_url":"https://codeload.github.com/kangalio/launchy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kangalio%2Flaunchy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274072333,"owners_count":25217751,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-07T02:00:09.463Z","response_time":67,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["launchpad","lightshow","midi","midi-controller","novation"],"created_at":"2024-10-13T10:58:43.307Z","updated_at":"2025-09-07T17:44:56.177Z","avatar_url":"https://github.com/kangalio.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# launchy\n\n[![docs.rs](https://docs.rs/launchy/badge.svg)](https://docs.rs/launchy)\n[![crates.io](https://img.shields.io/crates/v/launchy)](https://crates.io/crates/launchy)\n[![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/kangalioo/launchy/blob/master/LICENSE)\n\nAn exhaustive Rust API for the Novation Launchpad devices, optimized for maximum expressiveness and minimum boilerplate!\n\n\u003ca href=\"https://youtu.be/DHwv7yu5dJc\"\u003e\u003cimg src=\"https://imgur.com/gBKAjgS.jpg\" width=\"50%\"/\u003e\u003c/a\u003e\n\n---\n\nLaunchy is a library for the Novation Launchpad MIDI devices, for the Rust programming language.\n\n## Features\n\n- the **Canvas API** provides a powerful and concise way to control your Launchpads, for games or small lightshows\n- the **direct Input/Output API** provides absolute control over your device and fine-grained access to the entire MIDI API of your device\n- it's possible to chain multiple Launchpads together and use them as if it was one single big device\n- optional support for [`embedded-graphics`](https://github.com/jamwaffles/embedded-graphics)\n- very modular design: it's very easy to add support for new devices, or to add new features to [`Canvas`]\n\n## Supported devices\n- [ ] Launchpad\n- [x] Launchpad S\n- [x] Launchpad Mini\n- [x] Launchpad Control\n- [x] Launchpad Control XL\n- [ ] Launchpad Pro\n- [x] Launchpad MK2\n- [ ] Launchpad X\n- [ ] Launchpad Mini MK3\n- [ ] Launchpad Pro MK2\n\n## Canvas API\nLaunchy provides a `Canvas` trait that allows you to abstract over the hardware-specific details of your Launchpad and write concise, performant and\nLaunchpad-agnostic code.\n\nThe `Canvas` API even allows you to chain multiple Launchpads together and use them as if they were a single device. See `CanvasLayout` for that.\n\n## Direct Input/Output API\nIn cases where you need direct access to your device's API, the abstraction provided by the `Canvas` API gets in your way.\n\nSay if you wanted to programmatically retrieve the firmware version of your Launchpad MK2:\n```rust\nlet input = launchy::mk2::Input::guess_polling()?;\nlet mut output = launchy::mk2::Output::guess()?;\n\noutput.request_version_inquiry()?;\nfor msg in input.iter() {\n    if let launchy::mk2::Message::VersionInquiry { firmware_version, .. } = msg {\n        println!(\"The firmware version is {}\", firmware_version);\n    }\n}\n```\n\n## Examples\n### Satisfying pulse light effect\n\u003ca href=\"https://youtu.be/DHwv7yu5dJc\"\u003e\u003cimg src=\"https://imgur.com/gBKAjgS.jpg\" width=\"50%\"/\u003e\u003c/a\u003e\n\n```rust\n// Setup devices\nlet (mut canvas, poller) = launchy::CanvasLayout::new_polling();\ncanvas.add_by_guess_rotated::\u003claunchy::control::Canvas\u003e(0, 14, launchy::Rotation::Right)?;\ncanvas.add_by_guess_rotated::\u003claunchy::mk2::Canvas\u003e(10, 18, launchy::Rotation::UpsideDown)?;\ncanvas.add_by_guess_rotated::\u003claunchy::s::Canvas\u003e(2, 8, launchy::Rotation::Right)?;\nlet mut canvas = canvas.into_padded();\n\n// Do the actual animation\nfor color in (0u64..).map(|f| Color::red_green_color(f as f32 / 60.0 / 2.5)) {\n    for msg in poller.iter_for_millis(17).filter(|msg| msg.is_press()) {\n        canvas[msg.pad()] = color * 60.0;\n    }\n    canvas.flush()?;\n\n    for pad in canvas.iter() {\n        let surrounding_color = pad.neighbors_5().iter()\n                .map(|\u0026p| canvas.get(p).unwrap_or(Color::BLACK))\n                .sum::\u003cColor\u003e() / 5.0 / 1.05;\n\n        canvas[pad] = canvas[pad].mix(surrounding_color, 0.4);\n    }\n}\n```\n\n\u003c!--### Snake game\n```rust\nlet (mut canvas, poller) = launchy::mk2::Canvas::guess_polling()?;\n\n// Initialize snake\nlet mut snake = std::collections::VecDeque::new();\nsnake.push_front(Pad { x: 0, y: 1 });\nsnake.push_front(Pad { x: 1, y: 1 });\nsnake.push_front(Pad { x: 2, y: 1 });\n\n// Set initial snake direction and pellet position\nlet mut direction = (1, 0);\nlet mut pellet = Pad { x: 5, y: 6 };\n\nloop {\n    for msg in poller.iter_for(Duration::from_millis(500)).filter(|msg| msg.is_press()) {\n        match msg.pad() {\n            Pad { x: 0, y: 0 } =\u003e direction = (0, -1),\n            Pad { x: 1, y: 0 } =\u003e direction = (0, 1),\n            Pad { x: 2, y: 0 } =\u003e direction = (-1, 0),\n            Pad { x: 3, y: 0 } =\u003e direction = (1, 0),\n            _ =\u003e {},\n        }\n    }\n\n    if snake.contains(\u0026(snake[0] + direction)) {\n        break;\n    } else {\n        snake.push_front(snake[0] + direction);\n    }\n\n    if snake[0] == pellet {\n        pellet = Pad { x: (rand::random() * 9) as i32, y: (rand::random() * 9) as i32 };\n    } else {\n        snake.pop_back();\n    }\n\n    canvas.clear();\n    for \u0026pad in \u0026snake {\n        canvas[pad] = Color::YELLOW;\n    }\n    canvas[pellet] = Color::GREEN;\n    canvas.flush()?;\n}\n```--\u003e\n\n### Seamless text scrolling across multiple Launchpads (leveraging `embedded_graphics`)\n\u003ca href=\"https://youtu.be/BJqoH3p9mhE\"\u003e\u003cimg src=\"https://imgur.com/Fxe9al9.jpg\" width=\"50%\"/\u003e\u003c/a\u003e\n\n(This image shows the first three letters of the word \"Hello\")\n\n```rust\nuse embedded_graphics::{fonts::{Font6x8, Text}, prelude::{Drawable, Point}, style::TextStyle};\n\n// Setup the Launchpad layout\nlet mut canvas = launchy::CanvasLayout::new(|_msg| {});\ncanvas.add_by_guess_rotated::\u003claunchy::control::Canvas\u003e(0, 14, launchy::Rotation::Right)?;\ncanvas.add_by_guess_rotated::\u003claunchy::mk2::Canvas\u003e(10, 18, launchy::Rotation::UpsideDown)?;\ncanvas.add_by_guess_rotated::\u003claunchy::s::Canvas\u003e(2, 8, launchy::Rotation::Right)?;\n\n// Do the text scrolling\nlet mut x_offset = 19;\nloop {\n    canvas.clear();\n\n    let t = Text::new(\"Hello world! :)\", Point::new(x_offset, 3))\n        .into_styled(TextStyle::new(Font6x8, Color::RED.into()))\n        .draw(\u0026mut canvas).unwrap();\n\n    canvas.flush()?;\n\n    sleep(100);\n    x_offset -= 1;\n}\n```\n\n## Why not just use [launch-rs](https://github.com/jamesmunns/launch-rs)?\n\n- Last commit in 2017\n- Only supports Launchpad MK2\n- Only low-level access to the Launchpad is provided. There is no way to write high-level, concise interfacing code\n- Uses the [PortMidi](https://github.com/musitdev/portmidi-rs) crate which is not as actively developed as [midir](https://github.com/Boddlnagg/midir), which Launchy uses\n- Doesn't have any of the advanced features that Launchy provides\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkangalio%2Flaunchy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkangalio%2Flaunchy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkangalio%2Flaunchy/lists"}