diff --git a/.gitmodules b/.gitmodules index 6e9c822..ec9f8cd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "definitions"] path = definitions url = git@github.com:renoise/definitions - branch = feature/api-docs + branch = master diff --git a/definitions b/definitions index b1fea2c..17af5c9 160000 --- a/definitions +++ b/definitions @@ -1 +1 @@ -Subproject commit b1fea2ccf4495736c7d24e848ef2ae1287b6b248 +Subproject commit 17af5c9f998d10cec9a65c5fd8f125ac451168ef diff --git a/docs/API/README.md b/docs/API/README.md index 426444a..f9ce2c0 100644 --- a/docs/API/README.md +++ b/docs/API/README.md @@ -1,29 +1,26 @@ # Renoise Lua API Overview -The XXX.API files in this documentation folder will list all available Lua -functions and classes that can be accessed from scripts in Renoise. -If you are familiar with Renoise, the names of the classes, functions and -properties should be self explanitory. +The API files in this documentation folder will list all available Lua functions and classes that can be accessed from scripts in Renoise. If you are familiar with Renoise, the names of the classes, functions and properties should be self explanitory. A note about the general API design: - Whatever you do with the API, you should never be able to fatally crash Renoise. If you manage to do this, then please file a bug report in our forums so we can fix it. All errors, as stupid they might be, should always result in a clean error message from Lua. -- The Renoise Lua API also allows global File IO and external program execution (via os.execute()) which can obviously be hazardous. Please be careful with these, as you would with programming in general... +- The Renoise Lua API also allows global File IO and external program execution (via `os.execute()`) which can obviously be hazardous. Please be careful with these, as you would with programming in general... Some notes about the documentation, and a couple of tips: -- All classes, functions in the API, are nested in the namespace (Lua table) "renoise". E.g: to get the application object, you will have to type "renoise.app()" +- All classes, functions in the API, are nested in the namespace (Lua table) `renoise`. E.g: to get the application object, you will have to type `renoise.app()` -- The API is object-oriented, and thus split into classes. The references will first note the class name (e.g. 'renoise.Application'), then list its Constants, Properties, Functions and Operators. All properties and functions are always listed with their full path to make it clear where they belong and how to access them. +- The API is object-oriented, and thus split into classes. The references will first note the class name (e.g. `renoise.Application`), then list its Constants, Properties, Functions and Operators. All properties and functions are always listed with their full path to make it clear where they belong and how to access them. -- Nearly all functions are actually "methods", so you have to invoke them via the colon operator ":" E.g. 'renoise.app():show_status("Status Message")' If you're new to Lua, this takes a while to get used to. Don't worry, it'll make sense sooner or later. ;) +- Nearly all functions are actually *methods*, so you have to invoke them via the colon operator `:` E.g. `renoise.app():show_status("Status Message")` If you're new to Lua, this takes a while to get used to. Don't worry, it'll make sense sooner or later. ;) -- Properties are syntactic sugar for get/set functions. "song().comments" will invoke a function which returns "comments". But not all properties have setters, and thus can only be used as read-only "getters". Those are marked as "**READ-ONLY**". +- Properties are syntactic sugar for get/set functions. `song().comments` will invoke a function which returns *comments*. But not all properties have setters, and thus can only be used as read-only *getters*. Those are marked as `**READ-ONLY**`. -- All exposed "objects" are read-only (you can not add new fields, properties). In contrast, the "classes" are not. This means you can extend the API classes with your own helper functions, if needed, but can not add new properties to objects. Objects, like for example the result of "song()", are read-only to make it easier to catch typos. `song().transport.bmp = 80` will fire an error, because there is no such property 'bmp.' You probably meant `song().transport.bpm = 80` here. If you need to store data somewhere, do it in your own tables, objects instead of using the Renoise API objects. +- All exposed *objects* are read-only (you can not add new fields, properties). In contrast, the *classes* are not. This means you can extend the API classes with your own helper functions, if needed, but can not add new properties to objects. Objects, like for example the result of `song()`, are read-only to make it easier to catch typos. `song().transport.bmp = 80` will fire an error, because there is no such property 'bmp.' You probably meant `song().transport.bpm = 80` here. If you need to store data somewhere, do it in your own tables, objects instead of using the Renoise API objects. -- "some_property, _observable" means, that there is also an observer object available for the property. An observable object allows you to attach notifiers (global functions or methods) that will be called as soon as a value has changed. Please see Renoise.Document.API for more info about observables and related classes. +- *some_property, _observable* means, that there is also an observer object available for the property. An observable object allows you to attach notifiers (global functions or methods) that will be called as soon as a value has changed. Please see Renoise.Document.API for more info about observables and related classes. A small example using bpm: ```lua @@ -37,6 +34,6 @@ renoise.song().transport.bpm = 120 The above notifier is called when anything changes the bpm, including your script, other scripts, or anything else in Renoise (you've automated the BPM in the song, entered a new BPM value in Renoise's GUI, whatever...) -Lists like "renoise.song().tracks[]" can also have notifiers. But these will only fire when the list layout has changed: an element was added, removed or elements in the list changed their order. They will not fire when the list values changed. Attach notifiers to the list elements to get such notifications. +Lists like `renoise.song().tracks[]` can also have notifiers. But these will only fire when the list layout has changed: an element was added, removed or elements in the list changed their order. They will not fire when the list values changed. Attach notifiers to the list elements to get such notifications. - Can't remember what the name of function XYZ was? In the scripting terminal you can list all methods/properties of API objects (or your own class objects) via the global function `oprint(some_object)` - e.g. `oprint(renoise.song())`. To dump the renoise module/class layout, use `rprint(renoise)`. \ No newline at end of file diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 1d4806c..46b77ce 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -11,12 +11,16 @@ - [Classes](guide/classes.md) - [Observables](guide/observables.md) - [Files & Bits](guide/files&bits.md) + - [Sockets](guide/sockets.md) - [MIDI](guide/midi.md) - [OSC](guide/osc.md) - [Pattern Iterator](guide/pattern_iterator.md) - [Track Automation](guide/track_automation.md) - [Sample Buffers](guide/sample_buffer.md) - - [*TODO*](guide/TODO.md) + - [Keybindings](guide/keybindings.md) + - [Menu Entries](guide/menus.md) + - [Views](guide/views.md) + - [Preferences](guide/preferences.md) - [API Reference](API/README.md) - [renoise](API/renoise.md) diff --git a/docs/guide/TODO.md b/docs/guide/TODO.md deleted file mode 100644 index 8672f1c..0000000 --- a/docs/guide/TODO.md +++ /dev/null @@ -1,172 +0,0 @@ -# TODO - -# The Song - -explain how the song is structured and provide some snippets to change things - -# Patterns and Phrases - -describe how to access the columns in patterns and phrases, mention iterators for performance - -# Keybindings - -show how keybindings work, explain scopes - -``` -keybindings: Register key bindings somewhere in Renoise's existing -set of bindings. - -The Lua table passed to add_keybinding is defined as: - -* Required fields: - + ["name"] = The scope, name and category of the key binding. - + ["invoke"] = A function that is called as soon as the mapped key is - pressed. The callback has one argument: "repeated", indicating - if its a virtual key repeat. -The key binding's 'name' must have 3 parts, separated by ":" e.g. -[scope:topic_name:binding_name] - -* 'scope' is where the shortcut will be applied, just like those - in the categories list for the keyboard assignment preference pane. -* 'topic_name' is useful when grouping entries in the key assignment pane. - Use "tool" if you can't come up with something meaningful. -* 'binding_name' is the name of the binding. - -Currently available scopes are: -> "Global", "Automation", "Disk Browser", "Instrument Box", "Mixer", -> "Pattern Editor", "Pattern Matrix", "Pattern Sequencer", "Sample Editor" -> "Track DSPs Chain" - -Using an unavailable scope will not fire an error, instead it will render the -binding useless. It will be listed and mappable, but never be invoked. - -There's no way to define default keyboard shortcuts for your entries. Users -manually have to bind them in the keyboard prefs pane. As soon as they do, -they'll get saved just like any other key binding in Renoise. - -``` - -# Menus - -explain how to create menu items and folders and their scopes - -``` - -menu_entries: Insert a new menu entry somewhere in Renoise's existing -context menus or the global app menu. Insertion can be done during -script initialization, but can also be done dynamically later on. - -The Lua table passed to 'add_menu_entry' is defined as: - -* Required fields: - + ["name"] = Name and 'path' of the entry as shown in the global menus or - context menus to the user - + ["invoke"] = A function that is called as soon as the entry is clicked - -* Optional fields: - + ["active"] = A function that should return true or false. When returning - false, the action will not be invoked and will be "greyed out" in - menus. This function is always called before "invoke", and every time - prior to a menu becoming visible. - + ["selected"] = A function that should return true or false. When - returning true, the entry will be marked as "this is a selected option" - -Positioning entries: - -You can place your entries in any context menu or any window menu in Renoise. -To do so, use one of the specified categories in its name: - -+ "Window Menu" -- Renoise icon menu in the window caption on Windows/Linux -+ "Main Menu" (:File", ":Edit", ":View", ":Tools" or ":Help") -- Main menu -+ "Scripting Menu" (:File", or ":Tools") -- Scripting Editor & Terminal -+ "Disk Browser Directories" -+ "Disk Browser Files" -+ "Instrument Box" -+ "Pattern Sequencer" -+ "Pattern Editor" -+ "Pattern Matrix" -+ "Pattern Matrix Header" -+ "Phrase Editor" -+ "Phrase Mappings" -+ "Phrase Grid" -+ "Sample Navigator" -+ "Sample Editor" -+ "Sample Editor Ruler" -+ "Sample Editor Slice Markers" -+ "Sample List" -+ "Sample Mappings" -+ "Sample FX Mixer" -+ "Sample Modulation Matrix" -+ "Mixer" -+ "Track Automation" -+ "Track Automation List" -+ "DSP Chain" -+ "DSP Chain List" -+ "DSP Device" -+ "DSP Device Header" -+ "DSP Device Automation" -+ "Modulation Set" -+ "Modulation Set List" - -Separating entries: - -To divide entries into groups (separate entries with a line), prepend one or -more dashes to the name, like "--- Main Menu:Tools:My Tool Group Starts Here" -``` - - -# Views - -overview of the view API - -``` --- Currently there are two ways to to create custom views: --- --- Shows a modal dialog with a title, custom content and custom button labels: -renoise.app():show_custom_prompt( - title, content_view, {button_labels} [, key_handler_func, key_handler_options]) - -> [pressed button] - --- _(and)_ Shows a non modal dialog, a floating tool window, with custom --- content: -renoise.app():show_custom_dialog( - title, content_view [, key_handler_func, key_handler_options]) - -> [dialog object] - --- key_handler_func is optional. When defined, it should point to a function --- with the signature noted below. "key" is a table with the fields: --- > key = { --- > name, -- name of the key, like 'esc' or 'a' - always valid --- > modifiers, -- modifier states. 'shift + control' - always valid --- > character, -- character representation of the key or nil --- > note, -- virtual keyboard piano key value (starting from 0) or nil --- > state, -- optional (see below) - is the key getting pressed or released? --- > repeated, -- optional (see below) - true when the key is soft repeated (held down) --- > } --- --- The "repeated" field will not be present when 'send_key_repeat' in the key --- handler options is set to false. The "state" field only is present when the --- 'send_key_release' in the key handler options is set to true. So by default only --- key presses are send to the key handler. --- --- key_handler_options is an optional table with the fields: --- > options = { --- > send_key_repeat=true OR false -- by default true --- > send_key_release=true OR false -- by default false --- > } --- > --- Returned "dialog" is a reference to the dialog the keyhandler is running on. --- --- function my_keyhandler_func(dialog, key) end --- --- When no key handler is specified, the Escape key is used to close the dialog. --- For prompts, the first character of the button labels is used to invoke --- the corresponding button. --- --- When returning the passed key from the key-handler function, the --- key will be passed back to Renoise's key event chain, in order to allow --- processing global Renoise key-bindings from your dialog. This will not work --- for modal dialogs. This also only applies to global shortcuts in Renoise, --- because your dialog will steal the focus from all other Renoise views such as --- the Pattern Editor, etc. -``` \ No newline at end of file diff --git a/docs/guide/classes.md b/docs/guide/classes.md index 5f88dac..bf84104 100644 --- a/docs/guide/classes.md +++ b/docs/guide/classes.md @@ -1,20 +1,10 @@ # Classes -Renoises lua API has a simple OO support inbuilt -> `class "MyClass"`. All -Renoise API objects use such classes. +The Lua language has by default no `class` construct. But the Renoises lua API has a simple OO support inbuilt -> `class "MyClass"`. All Renoise API objects use such classes and you can use them too in your tools. See [luabind docs](https://www.rasterbar.com/products/luabind/docs.html#defining-classes-in-lua) for more technical info and below for some simple examples -Something to keep in mind: - -* constructor `function MyClass:__init(args)` must be defined for each class, - or the class can't be used to instantiate objects. - -* class defs are always global, so even locally defined classes will be - registered globally. - - ## Examples ```lua @@ -74,6 +64,15 @@ for _,animal in pairs(farm) do end ``` +Something to keep in mind: + +* constructor `function MyClass:__init(args)` must be defined for each class, + or the class can't be used to instantiate objects. + +* class defs are always global, so even locally defined classes will be + registered globally. + + ## Class operators You can overload most operators in Lua for your classes. You do this by diff --git a/docs/guide/files&bits.md b/docs/guide/files&bits.md index 50d28bb..6dca992 100644 --- a/docs/guide/files&bits.md +++ b/docs/guide/files&bits.md @@ -1,5 +1,9 @@ # Files&Bits.lua +In order to access the raw bits and bytes of some data, e.g. to read or write binary (file) streams, you can use the Lua `bit` library. It's built into the Renoise API. There's no need to `require` it. + +See [bit documentation](https://bitop.luajit.org/) for more info and examples. + ```lua -- reading integer numbers or raw bytes from a file @@ -43,4 +47,3 @@ print(read_word(file) or "unexpected end of file") print(read_dword(file) or "unexpected end of file") ``` -more bit manipulation? -> See [bit documentation](https://bitop.luajit.org/) diff --git a/docs/guide/keybindings.md b/docs/guide/keybindings.md new file mode 100644 index 0000000..0852445 --- /dev/null +++ b/docs/guide/keybindings.md @@ -0,0 +1,57 @@ +# Keybindings + +Tools can add custom key bindings somewhere in Renoise's existing set of bindings, which will be activated and +mapped by the user just as any other key binding in Renoise. + +Keybindings can be global (applied everywhere in the GUI) or can be local to a specific part of the GUI, like the Pattern Editor. + +Please note: there's no way to define default keyboard shortcuts for your entries. Users manually have to bind them in the keyboard prefs pane. As soon as they do, they'll get saved just like any other key binding in Renoise. + +To do so, we are using the tool's [add_keybinding](../API/renoise/renoise.ScriptingTool.md#add_keybinding) function. + +### Example + +```lua +renoise.tool():add_keybinding { + name = "Global:Tools:Example Script Shortcut", + invoke = function(repeated) + if (not repeated) then -- we ignore soft repeated keys here + renoise.app():show_prompt( + "Congrats!", + "You've pressed a magic keyboard combo ".. + "which was defined by a scripting tool.", + {"OK?"} + ) + end + end +} +``` + +### Scopes + +The scope, name and category of the key binding use the form: +`$scope:$topic_name:$binding_name`: + +* `scope` is where the shortcut will be applied, just like those +in the categories list for the keyboard assignment preference pane. +Your key binding will only fire, when the scope is currently focused, +except it's the global scope one. +Using an unavailable scope will not fire an error, instead it will render +the binding useless. It will be listed and mappable, but never be invoked. + +* `topic_name` is useful when grouping entries in the key assignment pane. +Use "tool" if you can't come up with something meaningful. + +* `binding_name` is the name of the binding. + +Currently available scopes are: + +See API Docs for [KeybindingEntry](../API/renoise/renoise.ScriptingTool.md#toolkeybindingentry). + +### Separating entries + +To divide entries into groups (separate entries with a line), prepend one or more dashes to the name, like + +```lua +"--- Main Menu:Tools:My Tool Group Starts Here" +``` \ No newline at end of file diff --git a/docs/guide/menus.md b/docs/guide/menus.md new file mode 100644 index 0000000..a0e9dde --- /dev/null +++ b/docs/guide/menus.md @@ -0,0 +1,47 @@ +# Menu Entries + +You can add new menu entries into any existing context menus or the global menu in Renoise. + +To do so, we are using the tool's [add_menu_entry](../API/renoise/renoise.ScriptingTool.md#add_menu_entry) function. + +### Example + +```lua +renoise.tool():add_menu_entry { + name = "Main Menu:Tools:My Tool:Show Message...", + invoke = function() + renoise.app():show_prompt( + "Congrats!", + "You've pressed then 'Show Message...' menu entry from the tools menu, ".. + "which was defined by a scripting tool.", + {"OK?"} + ) + end +} +``` + +### Avilable Menus + +You can place your entries in any context menu or any window menu in Renoise. To do so, use one of the specified categories in its name. + +See API Docs for [ToolMenuEntry](../API/renoise/renoise.ScriptingTool.md#toolmenuentry) for more info. + +### Separating Entries + +To divide entries into groups (separate entries with a line), prepend one or more dashes to the name, like + +```lua +"--- Main Menu:Tools:My Tool Group Starts Here" +``` + +### Entry Sub Groups + +To move entries into a menu sub groups, use a common path for them, like + +```lua +"Main Menu:Tools:My Tool Group:First Entry" +"Main Menu:Tools:My Tool Group:Second Entry" +"Main Menu:Tools:My Tool Group:Third Entry" +``` + + diff --git a/docs/guide/midi.md b/docs/guide/midi.md index 51b9bc5..39747fa 100644 --- a/docs/guide/midi.md +++ b/docs/guide/midi.md @@ -1,12 +1,19 @@ # Midi +The Renoise API allows you to access raw MIDI input and output devices from within your tool. +You can use this to add bi-directional MIDI controller support, for example. + ## Midi input listener (function callback) ```lua -local inputs = renoise.Midi.available_input_devices() +-- NOTE: the midi device will be closed when the local variable gets garbage +-- collected. Make it global or assign it to something which is held globally +-- to avoid that. local midi_device = nil +local inputs = renoise.Midi.available_input_devices() if not table.is_empty(inputs) then + -- use the first avilable device in this example local device_name = inputs[1] local function midi_callback(message) @@ -60,20 +67,21 @@ class "MidiDumper" self.device_name, #message)) end +-- NOTE: the midi device will be closed when the local variable gets garbage +-- collected. Make it global or assign it to something which is held globally +-- to avoid that. +local midi_dumper = nil local inputs = renoise.Midi.available_input_devices() if not table.is_empty(inputs) then + -- use the first avilable device in this example local device_name = inputs[1] - - -- should be global to avoid premature garbage collection when - -- going out of scope. + midi_dumper = MidiDumper(device_name) - -- will dump till midi_dumper:stop() is called or the MidiDumber object - -- is garbage collected ... + -- is garbage collected... midi_dumper:start() - end ``` @@ -81,17 +89,16 @@ end ```lua local outputs = renoise.Midi.available_output_devices() - if not table.is_empty(outputs) then local device_name = outputs[1] - midi_device = renoise.Midi.create_output_device(device_name) + local midi_device = renoise.Midi.create_output_device(device_name) -- note on - midi_device:send {0x90, 0x10, 0x7F} + midi_device:send { 0x90, 0x10, 0x7F } -- sysex (MMC start) - midi_device:send {0xF0, 0x7F, 0x00, 0x06, 0x02, 0xF7} + midi_device:send { 0xF0, 0x7F, 0x00, 0x06, 0x02, 0xF7 } - -- no longer need the device... + -- no longer need the device in this example... midi_device:close() end ``` \ No newline at end of file diff --git a/docs/guide/observables.md b/docs/guide/observables.md index e31b423..322790e 100644 --- a/docs/guide/observables.md +++ b/docs/guide/observables.md @@ -2,7 +2,7 @@ The Renoise API makes extensive use of [the Observer pattern](https://en.wikipedia.org//wiki/Observer_pattern). In short, different values can be wrapped in Observable objects on which you can register notifier functions that will get called whenever the underlying value changes. This is a great way to react to changes without having to deal with constantly comparing the value to a cached state for example. -When reading the [API](https://github.com/renoise/definitions/), you will find a lot of fields with `_observable` at the end. Let's see how we can utilize this feature by attaching a notifier so that our tool can do something whenever the user loads a new song. +When reading the [API](../API/README.md), you will find a lot of fields with `_observable` at the end. Let's see how we can utilize this feature by attaching a notifier so that our tool can do something whenever the user loads a new song. ```lua local loaded_new_song = function() @@ -34,169 +34,3 @@ Now try changing the name of the current song inside the first field of the *Son With this technique you can listen to all sorts of things which is very useful when you want your tool to change its behaviour or initialize itself each time the user selects a new instrument, track, sample and so on. There are different Observables for each primitive type like `ObservableBoolean`, `ObservableNumber` or `ObservableString` and ones that are for storing list of such values (`ObservableBooleanList` etc.). You can check out the entire [Observables API](../API/renoise/renoise.Document.Observable.md) to see them all. - -# Preferences - -Tools can have preferences just like Renoise. To use them we first need to create a renoise.Document object which holds the options that we want to store and restore. - -Documents in Renoise have all of their fields defined as an Observable type. This comes extra handy when you want to create some settings dialog that needs to be able change the behaviour of your tool and remember its state, while you also want to access these settings across your tool. By using the built-in Document API you get a lot of this functionality for free. - -Let's see an example of setting up an `options` object that can be used for the above things. - -Our goal here is to have three kinds of settings managed by the Document class and a dialog that can be used to change them. The tool will be able to randomize the song by changing BPM and creating a random number of tracks. - -We will also define a menu entry for opening our tool settings dialog, you can read more about menu entries in the [Menus](TODO.md#menus) guide, graphical dialogs are further discussed in [Views](TODO.md#views). - -```lua --- We are creating a new renoise Document by supplying a table of values. --- It has three fields, which will all get wrapped in an Observable --- * a boolean for whether or not the tools should randomize the BPM --- * another boolean for randomizing tracks --- * an integer representing how many tracks we want to have at maximum -local options = renoise.Document.create("RandomizerToolPreferences") { - randomize_bpm = true, - randomize_tracks = false, - max_tracks = 16 -} - --- once we have our options, we have to assign it to our tool -renoise.tool().preferences = options - --- we define a randomizer function --- when called, it will set a random BPM --- and add or remove tracks randomly -function randomize_song() - local song = renoise.song() - -- make sure you use .value when accessing the underlying value inside Observables - if options.randomize_bpm.value then - -- we are setting the BPM to a value between 60 and 180 - song.transport.bpm = 60 + math.random() * 120 - end - - if options.randomize_tracks.value then - -- let's figure out how many tracks we want based on the max_tracks option - local target_count = 1 + math.floor(math.random() * options.max_tracks.value) - - -- and cache the amount of regular tracks the song has - local current_count = song.sequencer_track_count - - -- we will either insert new tracks if there aren't enough - if current_count < target_count then - for i = 1, target_count - current_count do - song:insert_track_at(current_count) - end - else - -- or remove them if there is too much - for i = 1, current_count - target_count do - song:delete_track_at(song.sequencer_track_count) - end - end - end -end - --- let's define a function for a custom dialog --- it will contain checkboxes and a slider for our options -function show_options() - local vb = renoise.ViewBuilder() - - local dialog_content = vb:vertical_aligner { - margin = renoise.ViewBuilder.DEFAULT_CONTROL_MARGIN, - views = { - vb:row { - views = { - vb:text { - text = "Randomize BPM" - }, - -- here we are binding our observable value to this checkbox - vb:checkbox { - bind = options.randomize_bpm - } - } - }, - vb:row { - views = { - vb:text { - text = "Randomize Tracks" - }, - -- the same thing for the boolean for tracks - vb:checkbox { - bind = options.randomize_tracks - } - } - }, - vb:row { - views = { - vb:text { - text = "Max Tracks" - }, - -- for the maximum tracks we create a value box - -- and restrict it to a range of [1..16] - vb:valuebox { - min = 1, - max = 16, - bind = options.max_tracks - } - } - }, - -- add a button that will execute the randomization based on our options - vb:row { - vb:button { - text = "Randomize", - pressed = randomize_song - } - } - } - } - renoise.app():show_custom_dialog( - "Randomizer Options", dialog_content - ) -end - --- finally we add a menu entry to open our options dialog -renoise.tool():add_menu_entry { - name = "Main Menu:Tools:Randomizer Options", - invoke = show_options -} - -``` - -As you can see all we had to do was to assign our observables to the `bind` field on the checkboxes and valuebox, Renoise will take care of updating our settings when the view changes and vice versa. - -Of course you could also use observables this way without them being included in your settings, but this is the most common usage pattern for them. - -## preferences.xml - -When you assign the preferences, Renoise will take care of saving and loading your settings. Your tool will have a `preferences.xml` file created in its folder with the values from your options table. As long as you are using simple types, you don't have to worry about (de)serializing values. - -Try restarting Renoise to see that the values you've set in your dialog will persist over different sessions. - -## Complex Documents - -For more complex applications (or if you just prefer doing things the Object-Oriented way) you can also inherit from `renoise.Document.DocumentNode` and register properties in the constructor. - -You could change the above Document creation to something like this: - -```lua ----@class RandomizerToolPreferences : renoise.Document.DocumentNode ----@field randomize_bpm renoise.Document.ObservableBoolean ----@field randomize_tracks renoise.Document.ObservableBoolean ----@field max_tracks renoise.Document.ObservableNumber -class "RandomizerToolPreferences" (renoise.Document.DocumentNode) - -function RandomizerToolPreferences:__init() - renoise.Document.DocumentNode.__init(self) - -- register an observable properties which will make up our Document - self:add_property("randomize_bpm", true) - self:add_property("randomize_tracks", false) - self:add_property("max_tracks", 16) -end - ----@type RandomizerToolPreferences -local options = RandomizerToolPreferences() - -renoise.tool().preferences = options -``` - -This allows you to create more complex documents. Have a look at the complete [Document API](https://github.com/renoise/definitions/blob/main/library/renoise/document.lua) for more info and details about what else you can load/store this way. - -> Note: This time we also included type annotations (like `---@class RandomizerToolPreferences`). These can help you with development but they aren't strictly necessary. You can read more about how to use them at the [LuaLS website](https://luals.github.io/). \ No newline at end of file diff --git a/docs/guide/osc.md b/docs/guide/osc.md index 6ad8c31..e5b0c0d 100644 --- a/docs/guide/osc.md +++ b/docs/guide/osc.md @@ -1,10 +1,12 @@ # Osc -NB: when using TCP instead of UDP as socket protocol, manual SLIP en/decoding -of OSC message data would be required too. This is left out here, so the examples -below only work with UDP servers/clients. +The Renoise API allows you to create web sockets and provides tools to receive and send OSC data. This allows you to connect your tools to other OSC servers and send OSC data to existing clients. -## Osc server (receive Osc from one or more clients) +In general, this can be used as an alternative to MIDI, e.g. to connect to OSC-enabled devices such as [monome](https://monome.org/docs/serialosc/osc). + +Note: Using TCP instead of UDP as the socket protocol would likely also require manual [SLIP encoding/decoding](https://en.wikipedia.org/wiki/Serial_Line_Internet_Protocol) of OSC message data. This is omitted here, so the examples below will only work with UDP servers/clients. + +## Osc Server (receive Osc from one or more Clients) ```lua -- create some shortcuts @@ -61,7 +63,7 @@ server:run { ``` -## Osc client & message construction (send Osc to a server) +## Osc Client & Message Construction (send Osc to a Server) ```lua -- create some shortcuts diff --git a/docs/guide/pattern_iterator.md b/docs/guide/pattern_iterator.md index 5a49677..2e03aed 100644 --- a/docs/guide/pattern_iterator.md +++ b/docs/guide/pattern_iterator.md @@ -1,4 +1,8 @@ -# PatternIterator +# Pattern Iterator + +As a quick and efficient way to access the pattern data in phrases and the Renoise song, you can use the existing pattern iterators. Here are a few examples of how to use them. + +See also API docs for [renoise.PatternIterator](../API/renoise/renoise.PatternIterator.md). ## Change notes in selection diff --git a/docs/guide/preferences.md b/docs/guide/preferences.md new file mode 100644 index 0000000..b37df91 --- /dev/null +++ b/docs/guide/preferences.md @@ -0,0 +1,165 @@ +# Preferences + +Tools can have preferences just like Renoise. To use them we first need to create a renoise.Document object which holds the options that we want to store and restore. + +Documents in Renoise have all of their fields defined as an Observable type. This comes extra handy when you want to create some settings dialog that needs to be able change the behaviour of your tool and remember its state, while you also want to access these settings across your tool. By using the built-in Document API you get a lot of this functionality for free. + +Let's see an example of setting up an `options` object that can be used for the above things. + +Our goal here is to have three kinds of settings managed by the Document class and a dialog that can be used to change them. The tool will be able to randomize the song by changing BPM and creating a random number of tracks. + +We will also define a menu entry for opening our tool settings dialog, you can read more about menu entries in the [Menus](menus.md) guide, graphical dialogs are further discussed in [Views](views.md). + +```lua +-- We are creating a new renoise Document by supplying a table of values. +-- It has three fields, which will all get wrapped in an Observable +-- * a boolean for whether or not the tools should randomize the BPM +-- * another boolean for randomizing tracks +-- * an integer representing how many tracks we want to have at maximum +local options = renoise.Document.create("RandomizerToolPreferences") { + randomize_bpm = true, + randomize_tracks = false, + max_tracks = 16 +} + +-- once we have our options, we have to assign it to our tool +renoise.tool().preferences = options + +-- we define a randomizer function +-- when called, it will set a random BPM +-- and add or remove tracks randomly +function randomize_song() + local song = renoise.song() + -- make sure you use .value when accessing the underlying value inside Observables + if options.randomize_bpm.value then + -- we are setting the BPM to a value between 60 and 180 + song.transport.bpm = 60 + math.random() * 120 + end + + if options.randomize_tracks.value then + -- let's figure out how many tracks we want based on the max_tracks option + local target_count = 1 + math.floor(math.random() * options.max_tracks.value) + + -- and cache the amount of regular tracks the song has + local current_count = song.sequencer_track_count + + -- we will either insert new tracks if there aren't enough + if current_count < target_count then + for i = 1, target_count - current_count do + song:insert_track_at(current_count) + end + else + -- or remove them if there is too much + for i = 1, current_count - target_count do + song:delete_track_at(song.sequencer_track_count) + end + end + end +end + +-- let's define a function for a custom dialog +-- it will contain checkboxes and a slider for our options +function show_options() + local vb = renoise.ViewBuilder() + + local dialog_content = vb:vertical_aligner { + margin = renoise.ViewBuilder.DEFAULT_CONTROL_MARGIN, + views = { + vb:row { + views = { + vb:text { + text = "Randomize BPM" + }, + -- here we are binding our observable value to this checkbox + vb:checkbox { + bind = options.randomize_bpm + } + } + }, + vb:row { + views = { + vb:text { + text = "Randomize Tracks" + }, + -- the same thing for the boolean for tracks + vb:checkbox { + bind = options.randomize_tracks + } + } + }, + vb:row { + views = { + vb:text { + text = "Max Tracks" + }, + -- for the maximum tracks we create a value box + -- and restrict it to a range of [1..16] + vb:valuebox { + min = 1, + max = 16, + bind = options.max_tracks + } + } + }, + -- add a button that will execute the randomization based on our options + vb:row { + vb:button { + text = "Randomize", + pressed = randomize_song + } + } + } + } + renoise.app():show_custom_dialog( + "Randomizer Options", dialog_content + ) +end + +-- finally we add a menu entry to open our options dialog +renoise.tool():add_menu_entry { + name = "Main Menu:Tools:Randomizer Options", + invoke = show_options +} + +``` + +As you can see all we had to do was to assign our observables to the `bind` field on the checkboxes and valuebox, Renoise will take care of updating our settings when the view changes and vice versa. + +Of course you could also use observables this way without them being included in your settings, but this is the most common usage pattern for them. + +## preferences.xml + +When you assign the preferences, Renoise will take care of saving and loading your settings. Your tool will have a `preferences.xml` file created in its folder with the values from your options table. As long as you are using simple types, you don't have to worry about (de)serializing values. + +Try restarting Renoise to see that the values you've set in your dialog will persist over different sessions. + +## Complex Documents + +For more complex applications (or if you just prefer doing things the Object-Oriented way) you can also inherit from `renoise.Document.DocumentNode` and register properties in the constructor. + +You could change the above Document creation to something like this: + +```lua +---@class RandomizerToolPreferences : renoise.Document.DocumentNode +---@field randomize_bpm renoise.Document.ObservableBoolean +---@field randomize_tracks renoise.Document.ObservableBoolean +---@field max_tracks renoise.Document.ObservableNumber +class "RandomizerToolPreferences" (renoise.Document.DocumentNode) + +function RandomizerToolPreferences:__init() + renoise.Document.DocumentNode.__init(self) + -- register an observable properties which will make up our Document + self:add_property("randomize_bpm", true) + self:add_property("randomize_tracks", false) + self:add_property("max_tracks", 16) +end + +---@type RandomizerToolPreferences +local options = RandomizerToolPreferences() + +renoise.tool().preferences = options +``` + +This allows you to create more complex documents. Have a look at the complete [Document API](https://github.com/renoise/definitions/blob/main/library/renoise/document.lua) for more info and details about what else you can load/store this way. + +> Note: This time we also included type annotations (like `---@class RandomizerToolPreferences`). These can help you with development but they aren't strictly necessary. You can read more about how to use them at the [LuaLS website](https://luals.github.io/). \ No newline at end of file diff --git a/docs/guide/sockets.md b/docs/guide/sockets.md index c7e63da..bcae972 100644 --- a/docs/guide/sockets.md +++ b/docs/guide/sockets.md @@ -1,10 +1,12 @@ # Sockets +The Renoise API allows you to create [network sockets](https://en.wikipedia.org/wiki/Network_socket). This can be used to communicate with other devices via UDP and TCP, e.g. to send or receive [OSC messages](./osc.md). + +> Please note that there's no support for encrypted connections. So using protocols like HTTPs is not easily possible with the socket API in Renoise. ## HTTP / GET client -Creates a TCP socket and connect it to www.wurst.de, http, giving up -the connection attempt after 2 seconds. +Creates a TCP socket and connect it to www.wurst.de, http, giving up the connection attempt after 2 seconds. ```lua local connection_timeout = 2000 @@ -77,7 +79,7 @@ else end ``` -## Echo udp server (using a table as notifier) +## Echo UDP Server (using a table as notifier) ```lua local server, socket_error = renoise.Socket.create_server( @@ -88,15 +90,19 @@ if socket_error then "Failed to start the echo server: " .. socket_error) else server:run { + ---@param socket_error string socket_error = function(socket_error) renoise.app():show_warning(socket_error) end, + ---@param socket renoise.Socket.SocketClient socket_accepted = function(socket) print(("client %s:%d connected"):format( socket.peer_address, socket.peer_port)) end, + ---@param socket renoise.Socket.SocketClient + ---@param message string socket_message = function(socket, message) print(("client %s:%d sent '%s'"):format( socket.peer_address, socket.peer_port, message)) @@ -105,11 +111,10 @@ else end } end - -- will run and echo as long as the script runs... ``` -## Echo TCP server (using a class as notifier) +## Echo TCP Server (using a class as notifier) ...and allowing any addresses to connect by not specifying an address: @@ -129,15 +134,19 @@ class "EchoServer" end end + ---@param socket_error string function EchoServer:socket_error(socket_error) renoise.app():show_warning(socket_error) end + ---@param socket renoise.Socket.SocketClient function EchoServer:socket_accepted(socket) print(("client %s:%d connected"):format( socket.peer_address, socket.peer_port)) end + ---@param message string + ---@param socket renoise.Socket.SocketClient function EchoServer:socket_message(socket, message) print(("client %s:%d sent '%s'"):format( socket.peer_address, socket.peer_port, message)) @@ -150,5 +159,4 @@ local echo_server = EchoServer(1025) -- will run and echo as long as the script runs or the EchoServer -- object is garbage collected... -``` - +``` \ No newline at end of file diff --git a/docs/guide/track_automation.md b/docs/guide/track_automation.md index ea94ee8..b321acb 100644 --- a/docs/guide/track_automation.md +++ b/docs/guide/track_automation.md @@ -1,4 +1,4 @@ -# TrackAutomation +# Track Automation ## Access the selected parameters automation diff --git a/docs/guide/views.md b/docs/guide/views.md new file mode 100644 index 0000000..4cb7e61 --- /dev/null +++ b/docs/guide/views.md @@ -0,0 +1,189 @@ +# Views + +Currently there are two ways to to create custom views in Tools: + +```lua +-- Shows a modal dialog with a title, custom content and custom button labels: +renoise.app():show_custom_prompt( + title, content_view, {button_labels} [, key_handler_func, key_handler_options]) + -> [pressed button] +``` + +See API Docs for [show_custom_prompt](../API/renoise/renoise.Application.md#show_custom_prompt) for more info. + + +```lua +-- Shows a non modal dialog, a floating tool window, with custom content: +renoise.app():show_custom_dialog( + title, content_view [, key_handler_func, key_handler_options]) + -> [dialog object] +``` + +See API Docs for [show_custom_dialog](../API/renoise/renoise.Application.md#show_custom_prompt) for more info. + +The `key_handler_func` in the custom dialog is optional. When defined, it should point to a function +with the signature noted below. + +See API Docs for [KeyHandler](../API/renoise/renoise.Application.md#key_handler) for more info. + + +## Creating Custom Views + +Widgets are created with the `renoise.ViewBuilder` class in the renoise API. + +See API Docs for [ViewBuilder](../API/renoise/renoise.ViewBuilder.md) for more info. + +### Hello World + +```lua +-- We start by instantiating a view builder object. We'll use it to create views. +local vb = renoise.ViewBuilder() + +-- Now we are going to use a "column" view. A column can do three things: +-- 1. showing a background (if you don't want your views on the plain dialogs +-- back) +-- 2. "stack" other views (its child views) either vertically, or horizontally +-- vertically = vb:column{} +-- horizontally = vb:row{} +-- 3. align child views via "margins" -> borders for nested views + +local dialog_title = "Hello World" +local dialog_buttons = { "OK" } + +-- fetch some consts to let the dialog look like Renoises default views... +local DEFAULT_MARGIN = renoise.ViewBuilder.DEFAULT_CONTROL_MARGIN + +-- start with a 'column' view, to stack other views vertically: +local dialog_content = vb:column { + -- set a border of DEFAULT_MARGIN around our main content + margin = DEFAULT_MARGIN, + + -- and create another column to align our text in a different background + vb:column { + -- background style that is usually used for "groups" + style = "group", + -- add again some "borders" to make it more pretty + margin = DEFAULT_MARGIN, + + -- now add the first text into the inner column + vb:text { + text = "from the Renoise Scripting API\n".. + "in a vb:column with a background" + }, + } +} + +-- show the custom content in a prompt +renoise.app():show_custom_prompt( + dialog_title, dialog_content, dialog_buttons) +``` + +#### Dynamic Content + +GUIs usually are dynamic. They interact with the user. Do do so, you'll need to memorize references to some of your widgets. Here's an example on how this works in the Renoise View API. + +```lua +local vb = renoise.ViewBuilder() + +-- we've used above an inlined style to create view. This is very elegant +-- when creating only small & simple GUIs, but can also be confusing when the +-- view hierarchy gets more complex. +-- you actually can also build views step by step, instead of passing a table +-- with properties, set the properties of the views manually: + +local DEFAULT_DIALOG_MARGIN = renoise.ViewBuilder.DEFAULT_DIALOG_MARGIN + +-- this: +local my_column_view = vb:column{} +my_column_view.margin = DEFAULT_DIALOG_MARGIN +my_column_view.style = "group" + +local my_text_view = vb:text{} +my_text_view.text = "My text" +my_column_view:add_child(my_text_view) + +-- is exactly the same like this: +local my_column_view = vb:column{ + margin = DEFAULT_DIALOG_MARGIN, + style = "group", + vb:text{ + text = "My text" + } +} + +-- In practice you should use a combination of the above two notations, but +-- its recommended to setup & prepare components in separate steps while +-- still using the inlined / nested notation: + +local my_first_column_view = vb:column { + -- some content +} + +local my_second_column_view = vb:column { + -- some more content +} + +-- Then do the final layout: +local my_final_layout = vb:row { + my_first_column_view, + my_second_column_view +} + +-- The inlined notation has a problem though: You can not memorize your views +-- in local variables, in case you want to access them later (for example to +-- hide/how them, change the text or whatever else). This is what viewbuilder +-- "id"s are for. + +-- Lets build up a simple view that dynamically reacts on a button hit: + +local DEFAULT_DIALOG_MARGIN = renoise.ViewBuilder.DEFAULT_DIALOG_MARGIN +local DEFAULT_CONTROL_SPACING = renoise.ViewBuilder.DEFAULT_CONTROL_SPACING + +local dialog_title = "vb IDs" +local dialog_buttons = {"OK"}; + +local dialog_content = vb:column { + margin = DEFAULT_DIALOG_MARGIN, + spacing = DEFAULT_CONTROL_SPACING, + + vb:text { + id = "my_text", -- we're giving the view a unique id here + text = "Do what you see" + }, + + vb:button { + text = "Hit Me!", + tooltip = "Hit this button to change the above text.", + notifier = function() + -- here we resolve the id and access the above constructed view + local my_text_view = vb.views.my_text + my_text_view.text = "Button was hit." + end + } +} + +-- We are doing two things here: +-- First we do create a vb:text as usual, but this time we also give it an +-- id "my_text_view". This id can then at any time be used to resolve this +-- view. So we can use the inlined notation without having to create lots of +-- local view refs + +-- There's now also a first control present: a button. Controls may have +-- notifiers. +-- The buttons notifier is simply a function without arguments, which is +-- called as soon as you hit the button. Tf you use other views like a +-- value box, the notifiers will pass a value along your function... + +-- Please note that ids are unique !per viewbuilder object!, so you can +-- create several viewbuilders (one for each component) to access multiple +-- sets of ids. + +renoise.app():show_custom_prompt( + dialog_title, dialog_content, dialog_buttons) +``` + +### More Examples + +See [com.renoise.ExampleToolGUI.xrnx](https://github.com/renoise/xrnx/tree/master/tools) for more examples. This tool be read as it's own little guide and provides a lot more in-depth examples. + +The example tools can also be downloaded as part of a XRNX Starter pack Bundle from the [releases page](https://github.com/renoise/xrnx/releases). diff --git a/docs/start/README.md b/docs/start/README.md index 8c04331..d82dd4e 100644 --- a/docs/start/README.md +++ b/docs/start/README.md @@ -5,4 +5,4 @@ If you are only interested in downloading tools for Renoise and not in developing your own tools, please have a look at the [Renoise Tools Page](http://tools.renoise.com/). -If you want to develop your own tools for Renoise, just keep reading :) \ No newline at end of file +If you want to develop your own tools for Renoise, just keep reading. \ No newline at end of file diff --git a/docs/start/development.md b/docs/start/development.md index 51df382..9fe0288 100644 --- a/docs/start/development.md +++ b/docs/start/development.md @@ -3,7 +3,7 @@ By default Renoise has all scripting utilities hidden to keep it simple for users who don't wish to mess around with code. If you want to write scripts, the first thing you have to do is to enable the hidden development tools. * For a quick test you can launch the Renoise executable with the `--scripting-dev` argument -* To have this mode enabled by default, you'll have to edit your `Config.xml` file inside Renoise's preferences folder. Search for the `` property and set it to `true`. +* To have this mode enabled by default, you'll have to edit your `Config.xml` file inside Renoise's preferences folder. Search for the `` property and set it to `true`. To reveal the Config.xml path, click on the *Help / Show the Preferences Folder...* menu entry in Renoise. Once scripting is enabled, you'll have the following entries inside the *Tools* menu on the top bar. diff --git a/docs/start/possibilities.md b/docs/start/possibilities.md index 8dd4804..46797b2 100644 --- a/docs/start/possibilities.md +++ b/docs/start/possibilities.md @@ -1,6 +1,6 @@ # Possibilities -Before you delve into writing your own scripts and tools, it worth considering what is even possible to do with them. In general, you can automate and manage different aspects Renoise, add or modify song data using algorithms or create interfaces to connect with other software or hardware. You can see some examples below about what you can access inside Renoise, for a complete reference, check out the [API](https://github.com/renoise/definitions/). +Before you delve into writing your own scripts and tools, it worth considering what is even possible to do with them. In general, you can automate and manage different aspects Renoise, add or modify song data using algorithms or create interfaces to connect with other software or hardware. You can see some examples below about what you can access inside Renoise, for a complete reference, check out the [API Definitions](../API/README.md). ### Process song data * Generate, modify or filter notes, patterns or phrases @@ -36,10 +36,10 @@ Before you delve into writing your own scripts and tools, it worth considering w There are a few ways tool creators can make the functionality they provide available for users, below is a brief summary of the most used methods. -* [Define keybindings](../guide/TODO.md#keybindings) that can be assigned to shortcuts and executed from certain contexts in Renoise -* [Add new entries to menus](../guide/TODO.md#menus) like the *Tools* menu or one of the right-click context menus -* [Create custom views](../guide/TODO.md#views) that do things on button presses, slider drags and so on -* [Listen to MIDI, OSC or WebSocket messages](../guide/TODO.md#midi) to execute actions +* [Define keybindings](../guide/keybindings.md) that can be assigned to shortcuts and executed from certain contexts in Renoise +* [Add new entries to menus](../guide/menus.md) like the *Tools* menu or one of the right-click context menus +* [Create custom views](../guide/views.md) that do things on button presses, slider drags and so on +* Listen to [MIDI](../guide/midi.md), [OSC](../guide/osc.md) or [WebSocket messages](../guide/sockets.md) to execute actions * [React to events inside Renoise](../guide/observables.md) like "do something any time a new song is loaded" ```xml @@ -53,7 +53,7 @@ Let's go through what each of these tags mean and what you should put inside the * `` is the version of your tool, whenever you release a new update you should increase this. It is best to follow standard [semantic versioning](https://semver.org/) conventions here. * `` contains your name and contact information, whenever your tool crashes, this information is going to be provided for the user alongside the crash message, you should use some contact where you can accept possbile bug reports or questions * `` the human readable name of your tool, it can be anything you want and you can change it anytime you feel like it -* `` the category for your tool, see the [categories you can choose from](../guide/TODO.md#tool-categories), this will be used to sort your tool on the [official Tools page](https://www.renoise.com/tools) if you ever decide to submit it there +* `` the category for your tool, which will be used to sort your tool on the [official Tools page](https://www.renoise.com/tools) if you ever decide to submit it there * `` a short description of your tool which will be displayed inside the *Tool Browser* in Renoise and on the Tools page * `` your website's address if you have any, you could also put your forum topic or git repository here if you want * `` the path to an optional icon to your tool