{"id":46896763,"url":"https://github.com/joslarson/react-bitwig","last_synced_at":"2026-03-10T23:32:39.254Z","repository":{"id":143046345,"uuid":"614707330","full_name":"joslarson/react-bitwig","owner":"joslarson","description":"⚛️ Build Bitwig Studio controller scripts in React!","archived":false,"fork":false,"pushed_at":"2024-04-11T14:10:00.000Z","size":238,"stargazers_count":29,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-10-08T20:57:42.049Z","etag":null,"topics":["bitwig-controller","bitwig-studio","react"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joslarson.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2023-03-16T06:34:48.000Z","updated_at":"2025-08-11T18:43:02.000Z","dependencies_parsed_at":"2023-05-13T02:45:43.896Z","dependency_job_id":null,"html_url":"https://github.com/joslarson/react-bitwig","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/joslarson/react-bitwig","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joslarson%2Freact-bitwig","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joslarson%2Freact-bitwig/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joslarson%2Freact-bitwig/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joslarson%2Freact-bitwig/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joslarson","download_url":"https://codeload.github.com/joslarson/react-bitwig/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joslarson%2Freact-bitwig/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30362120,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T21:41:54.280Z","status":"ssl_error","status_checked_at":"2026-03-10T21:40:59.357Z","response_time":106,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["bitwig-controller","bitwig-studio","react"],"created_at":"2026-03-10T23:32:38.958Z","updated_at":"2026-03-10T23:32:39.228Z","avatar_url":"https://github.com/joslarson.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ![React Bitwig](./docs/assets/react-bitwig-header.png)\n\n⚛️ Build Bitwig Studio controller scripts in React!\n\nReactBitwig is React based JavaScript framework for building controller scripts in Bitwig Studio. At its core is a custom React renderer for MIDI that enables declarative component based control of your devices via the provided `Midi` component. Built around and on top of this foundation is a suite of smart tools and helpers designed to improve the experience of working in Bitwig's unique JavaScript environment:\n\n- Custom state management solution that takes Bitwig's init phase into consideration.\n- Highly detailed TypeScript [type definitions](https://github.com/joslarson/typed-bitwig-api) for Bitwig APIs\n- A growing library of useful components, hooks, and other helpers\n- Built in debug tooling for Midi I/O logging and log filtering\n- [Custom Webpack plugin](https://github.com/joslarson/bitwig-webpack-plugin) enabling use of ES Modules and bundling of NPM packages with project\n- Polyfills for relevant missing browser API's (console, setTimeout/setInterval, etc.)\n\n## Quick Start\n\nFirst make sure you have the following:\n\n- Node.js v16 or newer\n- Bitwig Studio v4 or newer\n\n### Project Setup\n\nWith that out of the way, let's initialize a new project using ReactBitwig's CLI tool. We'll call our project `getting-started`.\n\n```bash\nnpx react-bitwig init getting-started\n# pass --typescript flag to initialize in TypeScript mode\n# run npx react-bitwig --help for detailed usage\n```\n\nThis command will ask you some questions then generate a working project skeleton based on your responses:\n\n```bash\n# answers are optional where a default is provided in parentheses\n[react-bitwig] begin project initialization...\nDisplay Name: Getting Started\nVendor/Category: Custom\nAuthor: Joseph Larson\nVersion (1.0.0):\nAPI Version (17):\n[react-bitwig] project initialization complete.\n```\n\nIf you answered the prompts as shown above your new project will be created within a new `getting-started` directory, relative to your current working directory, and will have the following structure:\n\n```bash\ngetting-started/\n├── dist/ -\u003e ...  # symlinked into default Bitwig control surface scripts directory\n├── src/\n│   └── getting-started.control.js\n├── README.md\n├── package.json\n├── tsconfig.json\n└── webpack.config.js\n```\n\n`cd` into your new project directory and install the initial dependencies using npm.\n\n```bash\ncd getting-started\nnpm install\n```\n\nNow, we'll run the `build` command, to generate your project's initial build.\n\n```bash\nnpm run build\n```\n\nAt this point your newly built project files should have been picked up by Bitwig and your script should be listed under `Custom \u003e Getting Started` in Bitwig's controller selection menu.\n\nActivate your new controller script by selecting it from the script selection menu and assigning your Midi controller's corresponding MIDI I/O.\n\n\u003e **Note**: The default project template is setup assuming a single midi input/output pair, but if you need more than that, more can be defined in the your project's entry file (`src/getting-started.control.js` in this case).\n\nNow, before we start editing files, let's run the build command in dev/watch mode so that our project will rebuild whenever we modify and save a source file.\n\n```bash\nnpm run dev # run from the project root\n# exit the build command with ctrl/cmd+c\n```\n\nWith that, everything should be in place to start coding your control surface script. Let's get into it :)\n\n### Creating Your First Component\n\nWith our bare bones project, our entry file should currently look something like this:\n\n```jsx\n// src/getting-started.control.js\n\nimport ReactBitwig, { ControllerScript } from 'react-bitwig';\n\nReactBitwig.render(\n  \u003cControllerScript\n    api={17}\n    vendor=\"Custom\"\n    name=\"Getting Started\"\n    version=\"1.0.0\"\n    uuid=\"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\"\n    author=\"Joseph Larson\"\n    midi={{ inputs: 1, outputs: 1 }}\n  \u003e\n    {/* ...add components to render here */}\n  \u003c/ControllerScript\u003e\n);\n```\n\nNot very useful yet... Let's jump in and create some init phase values/state and create and wire up our first component, the `PlayToggle`:\n\n```tsx\n// src/components/play-toggle.js\n\nimport React from 'react';\nimport ReactBitwig, { Midi } from 'react-bitwig';\n\n// `createInitValue` defines a gettable value that needs to be setup during the init phase\nconst TransportValue = ReactBitwig.createInitValue(() =\u003e\n  host.createTransport()\n);\n\n// The createInitState helper assists in wiring up init-time-only\n// subscriptions and provides a `.use()` hook for subscribing to\n// changes from within React components.\nconst IsPlayingState = ReactBitwig.createInitState(() =\u003e {\n  // You can safely access other init states and values inside this initializer\n  const transport = TransportValue.get();\n  transport\n    .isPlaying()\n    .addValueObserver((isPlaying) =\u003e IsPlayingState.set(isPlaying));\n  return false; // return initial state\n});\n\n// define our component using TransportValue and IsPlayingState form above\nexport const PlayToggle = () =\u003e {\n  // keep track of button pressed state so we can keep the button illuminated\n  // while the button is continues to be pressed when the transport is stopped\n  const [isPressed, setIsPressed] = React.useState(false);\n\n  // get the transport\n  const transport = TransportValue.get();\n  // subscribe to isPlaying transport state\n  const isPlaying = IsPlayingState.use();\n\n  return (\n    \u003cMidi\n      // useful for logging/debugging\n      label=\"PLAY\"\n      // defines the stable parts of MIDI messages associated with the control\n      pattern={{ status: 0xb0, data1: 0x18 }}\n      // messages matching the above pattern are now bound to this component\n      // and can be subscribed to via the onInput prop\n      onInput={({ data2 }) =\u003e {\n        // when pressing the button (data2 \u003e 0), toggle transport.isPlaying\n        if (data2 \u003e 0) transport.isPlaying().toggle();\n        // update local isPressed state so we can use it in determining the value below\n        setIsPressed(data2 \u003e 0);\n      }}\n      // when setting the value you only have to specify the non-stable parts of\n      // the message. We want the button light on if it is pressed or if the\n      // transport is playing.\n      value={{ data2: isPressed || isPlaying ? 127 : 0 }}\n      // caching modes teach ReactBitwig how to keep your hardware state in sync with your\n      // script and help optimize to avoid sending redundant MIDI messages\n      cacheOnInput // assume MIDI messages sent from the hardware to Bitwig represent a change in hardware state\n      cacheOnOutput // assume MIDI messages sent from Bitwig to the hardware represent a change in hardware state\n    /\u003e\n  );\n};\n```\n\n\u003e **Note:** Generally speaking, when this documentation mentions MIDI input or output, it is speaking from the perspective of the script, not the controller. So \"input\" is referring to MIDI messages traveling from the controller to the script, and \"output\" is referencing messages moving from the script to the controller.\n\nOnce you've finished building your `PlayToggle` component, import it into `getting-started.control.js` and render it as a child of your ControllerScript component:\n\n```tsx\n// src/getting-started.control.js\n\nimport ReactBitwig, { ControllerScript } from 'react-bitwig';\nimport { PlayToggle } from './components/play-toggle';\n\n// render our controller script\nReactBitwig.render(\n  \u003cControllerScript\n    api={17}\n    vendor=\"Custom\"\n    name=\"Getting Started\"\n    version=\"1.0.0\"\n    uuid=\"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\"\n    author=\"Joseph Larson\"\n    midi={{ inputs: 1, outputs: 1 }}\n  \u003e\n    \u003cPlayToggle /\u003e\n  \u003c/ControllerScript\u003e\n);\n```\n\n## The Midi Component\n\nThe Midi component allows you to declaratively send and/or receive MIDI messages to synchronize Midi hardware with your controller script state. The Midi component takes the following props:\n\n```ts\ntype MidiProps = {\n  // Name your control (helpful for logging/debugging)\n  label?: string;\n  // MIDI port messages are sent/received on, defaults to 0\n  port?: number;\n  // defines initial value to send on mount\n  defaultValue?: MidiObjectPattern;\n  // defines value to send on render\n  value?: MidiObjectPattern;\n  // defines value to send on on unmount\n  unmountValue?: MidiObjectPattern;\n  // defines the stable parts of MIDI messages associated with the control to subscribe to\n  pattern?: MidiHexPattern | MidiObjectPattern;\n  // called anytime a message is received matching the port and pattern defined above\n  onInput?: (message: MidiObjectPattern) =\u003e void;\n  // similar to onInput, but is only called when input represents a change to hardware state\n  onChange?: (message: MidiObjectPattern) =\u003e void;\n  // assume messages sent from hardware to script represent a change in hardware state\n  cacheOnInput?: boolean;\n  // assume messages sent from script to hardware represent a change in hardware state\n  cacheOnOutput?: boolean;\n  // sets the priority of a message, defaulting to false (messages sent during Bitwig's flush phase)\n  urgent?: boolean;\n};\n```\n\n### Sending MIDI Messages\n\nThe simplest use case for the Midi component is one where you just want to output a Midi message on mount/render/unmount. This can be accomplished using the `value`, `defaultValue`, and `unmountValue` props.\n\n```tsx\n// message sent on mount and render\n\u003cMidi value={{ status: 0xb0, data1: 0x18, data2: 0x00 }} /\u003e\n// message sent on mount and render if value has changed\n\u003cMidi value={{ status: 0xb0, data1: 0x18, data2: 0x00 }} cacheOnOutput /\u003e\n```\n\n```tsx\n// message sent on mount only\n\u003cMidi defaultValue={{ status: 0xb0, data1: 0x18, data2: 0x00 }} /\u003e\n```\n\n```tsx\n// message sent on unmount only (helpful to zero out a control display when it is no longer rendered)\n\u003cMidi unmountValue={{ status: 0xb0, data1: 0x18, data2: 0x00 }} /\u003e\n```\n\n### Responding to MIDI Input\n\nThe `pattern` prop of the Midi component defines which MIDI messages the component instance is subscribed to. The `onInput` props allows you to register a callback to that subscription.\n\n```tsx\n// callback passed to onInput will be called for all messages matching the provided pattern\n\u003cMidi pattern={{ status: 0xb0, data1: 0x18 }} onInput={(msg) =\u003e {...}} /\u003e\n```\n\nThe `onChange` prop can similarly be used to subscribe **only to changes** in the control's hardware state. For example, if the script receives a matching MIDI message that is identical to the current cached state, the onChange handler will not be called. However, this behavior only be leveraged when a component instance has configured its caching mode.\n\n```tsx\n\u003cMidi\n  pattern={{ status: 0xb0, data1: 0x18 }}\n  onChange={(msg) =\u003e {...}} // called only when virtual hardware state cache changes\n  cacheOnOutput // assume MIDI messages sent from Bitwig to the hardware represent a change in hardware state\n/\u003e\n```\n\n#### Midi Patterns\n\nPatterns can be defined in string or object literal form.\n\n- **Object literal form** allows you to define MIDI patterns by assuming undefined MIDI byte values to be wild cards (e.g. `{ status: 0xb0, data1: 0x18 }` where any MIDI message matching the provided values will be passed through). Though slightly less powerful and more verbose than string form patterns, you may find object literal form to be more readable while still covering the vast majority of use cases.\n\n- **String form** patterns are the most expressive and concise, representing the MIDI port and each MIDI byte as consecutive two character hexadecimal values, with question marks used for wildcard matching (e.g. `'B018??'` where `B0`, `18`, and `??` represent the status, data1, and data2 values).\n\n\u003e **Note**: The values for the status and data1 arguments above (e.g. `0xb0`) are just plain numbers written in JavaScript's hexadecimal (base 16) form. This makes it easier to to see the type of message and which channel the control maps to, but you can use regular base 10 integers 0-127 here if you want to.\n\n### Caching Modes\n\nThe `pattern`, `cacheOnInput`, and `cacheOnOutput` props work together to help ReactBitwig understand how to cache MIDI input/output messages such that redundant messages—those that would not change your MIDI controller's state—are not sent.\n\nThe caching mode configuration you choose will depending on your MIDI hardware (or virtual device). Passing the `cacheOnInput` prop tells ReactBitwig to assume that messages sent from hardware to script represent a change in hardware state. Similarly, the `cacheOnOutput` prop hints that messages sent from script to hardware represent a change in hardware state.\n\nAs an example, pretend you have a hardware button with and integrated light. It's a gate style button, which sends a `data2` of 127 when pressed and 0 when released. Now consider when you press this button (and a the 127 value input is received by your script), does the button light up while it is pressed? And what about when the script outputs a `data2` of 127 back to the hardware, does the button light up then?\n\nDifferent hardware implementations answer these questions in different ways. Some hardware will update the hardware state both when sending and receiving data, while others will only do so when receiving data, and still others allow you configure such behavior.\n\nBy default, the Midi component assumes no caching behavior at all, so it's important to understand how your hardware answers these questions and to configure Midi component instances accordingly.\n\n### Logging/Debugging Midi I/O \u0026 the `label` Prop\n\nBitwig provides a \"Controller Script Console\" for logging messages to debug your controller scripts. Leveraging this, ReactBitwig provides some extra built-in tooling to make debugging easier. In your controller script settings (for controller scripts built using ReactBitwig), you can turn on automatic logging of MIDI input, output, or both. MIDI I/O is logged in hexadecimal format and is tagged with the label provided to a matching Midi component instance if any. Sample log output:\n\n```\n// Format: [MIDI] IN/OUT PORT ==\u003e/\u003c== XXXXXX \"Control Label\"\n[MIDI]  IN  0 ==\u003e 91087F \"PLAY\"\n[MIDI]  OUT 0 \u003c== 91087F \"PLAY\"\n[MIDI]  IN  0 ==\u003e 910800 \"PLAY\"\n```\n\n## State Management\n\nBitwig has a unique runtime environment, and as part of that, you can only access some features during the \"init\" phase (which runs only once when your script first boots up). Basically any Bitwig data/event you would like to subscribe to (channel count, selected track, transport state, etc) has to be wired up during this phase, making things a bit awkward for a paradigm like React where it is common to initialize such subscriptions on mount and tear them down on unmount.\n\nTo improve the developer experience in relation to the restrictions placed on us by this \"init\" phase, ReactBitwig provides some \"init\" helpers to assist in defining init phase data and subscriptions/state.\n\n### `createInitValue`\n\nDefine a getter for an unchanging value, that is run only once during the init phase, but whose value can be requested any time thereafter (in our React components or elsewhere).\n\n```ts\n// `createInitValue` defines a gettable value that needs to be setup during the init phase\nconst TransportValue = ReactBitwig.createInitValue(() =\u003e\n  host.createTransport()\n);\n\n// Access value (after init)\nconst SomeComponent = () =\u003e {\n  const transport = TransportValue.get();\n  ...\n}\n```\n\n### `createInitObject`\n\nSimilar to `createInitValue`, but returns an object where key access is defined as getters underneath, so that it can be used like a regular object literal, without throwing errors before the init phase begins.\n\n```ts\nconst bitwig = ReactBitwig.createInitObject(() =\u003e {\n  const arrangerCursorClip = host.createArrangerCursorClip(4, 128);\n  const launcherCursorClip = host.createLauncherCursorClip(4, 128);\n\n  // transport\n  const transport = host.createTransport();\n  transport.subscribe();\n  transport.tempo().markInterested();\n  transport.getPosition().markInterested();\n  transport.isPlaying().markInterested();\n  transport.timeSignature().markInterested();\n\n  // application\n  const application = host.createApplication();\n  application.panelLayout().markInterested();\n\n  // cursorTrack\n  const cursorTrack = host.createCursorTrack(0, 16);\n  cursorTrack.isGroup().markInterested();\n  cursorTrack.color().markInterested();\n  cursorTrack.position().markInterested();\n\n  // trackBank\n  const trackBank = host.createMainTrackBank(8, 0, 0);\n  trackBank.cursorIndex().markInterested();\n  trackBank.channelCount().markInterested();\n  trackBank.setChannelScrollStepSize(8);\n\n\n  return {\n    arrangerCursorClip,\n    launcherCursorClip,\n    transport,\n    application,\n    cursorTrack,\n    trackBank,\n  };\n});\n\n\n// use it in a component as if it's a regular object literal\nconst SomeComponent = () =\u003e {\n  // note: destructuring must happen in the component\n  // (it will error if you destructure at the module level)\n  const { application,  transport } = bitwig;\n  ...\n}\n```\n\n### `createInitState`\n\nCreates an atomic piece of global state with an initializing function that is called during Bitwig's init phase. The resulting `InitState` instance provides a `.set(...)` method for updating the state, a `.use()` hook method for subscribing React components to the state, and `get/subscribe/unsubscribe` methods for accessing and subscribing to state outside of React components.\n\n```ts\n// The createInitState helper assists in wiring up init-time-only\n// subscriptions and provides a `.use()` hook for subscribing to\n// changes from within React components.\nconst IsPlayingState = ReactBitwig.createInitState(() =\u003e {\n  // You can safely access other init states and values inside this initializer\n  const transport = TransportValue.get();\n  transport\n    .isPlaying()\n    .addValueObserver((isPlaying) =\u003e IsPlayingState.set(isPlaying));\n\n  return false; // return initial state\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoslarson%2Freact-bitwig","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoslarson%2Freact-bitwig","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoslarson%2Freact-bitwig/lists"}