I just wanted to make a note on how helpful David Healey is in this community, and that his tireless efforts to upgrade everyone’s knowledge are noticed and appreciated!
Thank you, David :)
Happy New Year!
I just wanted to make a note on how helpful David Healey is in this community, and that his tireless efforts to upgrade everyone’s knowledge are noticed and appreciated!
Thank you, David :)
Happy New Year!
Christoph,
First off, I would like to thank you for this amazing development tool for music :)
I am quite happy to be taking the plunge and learning as much about coding in HISE every day since watching and following the David Healey tutorial video: "How to make a synth"
Now, my main goals with HISE involve making MIDI plugins; some to be released as free utilities, and others as a paid product.
In my tests it seems that HISE does not receive or pass (thru) some MIDI message types:
MIDI clock
MTC
MMC
SysEx
etc.
Then there is Program Change, which HISE can accept and use, but not generate or pass (thru).
My concern:
When using HISE to make MIDI plugins, blocking these messages may deny users access to certain features that are needed for the next plugin in the chain (often a VSTi).
Therefore, if I am to produce MIDI plugins I am apprehensive about blocking those messages, and I, myself as a MIDI plugin customer would not be happy if a plugin that I purchased were to deny the passage of those messages to the output stage.
Personally, I do a lot of orchestral template mockups in a system where the articulation switching of VSTis is managed by expression maps (in Dorico, Nuendo, Bidule and Reaper) which are changed via incoming program change messages. Currently, I can't actually use any of my HISE produced MIDI plugins within this system, as without PC messages, it blocks the articulation switching.
In addition, there are many VSTis that can utilize program change messages directly to change patches. Kontakt, Omnisphere and VSL for starters.
And from my observation, it seems that many electronic producers with outboard gear setups require MTC (MIDI time code) and/or MIDI clock to sync devices.
I also have use-cases for plugin designs involving MIDI clock, MTC and SysEx and would love to have access to them for making custom MIDI controller managers.
Given that, I would like to add a feature request of three different levels (sorted from lowest to highest complexity):
Thanks for your time,
Craig
@clevername27 Thank you very much for your time!
You have a lot of incite to provide and I found the instruction as well as conversation to be quite worthwhile. I would certainly sign up again for more!
@d-healey said in HISE Meet Up:
@HISEnberg said in HISE Meet Up:
I don't think you could do spectral morphing in realtime
https://www.zynaptiq.com/morph/
https://web.archive.org/web/20180705061655/http://www.hakenaudio.com/RealTimeMorph/
I find the Kyma to be rather convincing for smooth morphing:
https://www.youtube.com/watch?v=nt9tXXaXRrM
I would love to hear this kind of thing used to interpolate between sample sets for articulations that have capabilities for continuous control of timbre:
bow position, fluttertongue, growl, stick position on percussion etc.
There are a few different algorithms to choose from for morphing on the Kyma, and I don't know the details, but I think these are precomputed FFTs for resynthesis.
I am not sure what is causing this, but sometimes when I open a project/preset I am not getting any MIDI input and when I check the settings the MIDI inputs have been turned off:
Whereas I am certain I have saved it like this:
Yes, you can send MIDI out from button clicks.
I am working on a series of MIDI plugins at the moment.
You need to enable MIDI out in settings:
When using UI components to generate MIDI notes, you need to use Synth.addNoteOn() and Synth.addNoteOff() on the callback to you component.
Here I have pads (made with panels) which play chords when you use a mouse click.
It turns the notes off when the mouse is up.
// mouse callbacks for the pads --
pad.setMouseCallback(function[unitSize](event)
{
var ps = this.data.pitchSet;
var l = ps.length;
if (event.clicked)
{
var velocity = Math.round(event.y / unitSize * 127);
for (k = 0; k < l; k++)
Synth.addNoteOn(1, ps[k], velocity, 0);
}
if (event.mouseUp)
{
for (k = 0; k < l; k++)
Synth.addNoteOff(1, ps[k], 0);
}
});
To get the MIDI to output from the plugin,
as others have said you must use Message.sendToMidiOut();
To make sure it catches everything (and not some intermediate stage of MIDI),
I always place these functions in a script processor and container after all of my other containers:
And place the Message.sendToMidiOut() on each of the three MIDI callbacks:
@HISEnberg said in Let’s Build the “League of Newbies”:
@HISEnberg Done, I created the group chat. I guess anyone else who is interested can post here and we can add them in.
Add me :)
I'll check it out.
@d-healey It needs both ceil and floor to work, so I wrote an inline function to do the job:
inline function roundFix(value)
{
local remainder = Math.fmod(value, 1.0);
local roundedValue;
remainder >= 0.5 ? roundedValue = Math.ceil(value) : roundedValue = Math.floor(value);
return roundedValue;
};
const testRound = roundFix(4.49);
Console.print("test round: " + testRound);
const testRound1 = roundFix(4.5);
Console.print("test round: " + testRound1);
@d-healey said in Is there a method to rotate a path?:
@VirtualVirgin Instead of making a function to rotate a path, why not make a function to create a rotated path. You can pass in the data you would have used to create the initial path.
So based on your suggestion I came up with this and it seems to work:
inline function createRotatedPath(pathArray, angle, area)
{
local rad = Math.toRadians(angle);
local cosA = Math.cos(rad);
local sinA = Math.sin(rad);
local path = Content.createPath();
local rotationCenterX = area[0] + (area[2] * 0.5);
local rotationCenterY = area[1] + (area[3] * 0.5);
for (i = 0; i < pathArray.length; i++)
{
local x = pathArray[i][0];
local y = pathArray[i][1];
local newX = (x - rotationCenterX) * cosA - (y - rotationCenterY) * sinA + rotationCenterX;
local newY = (x - rotationCenterX) * sinA + (y - rotationCenterY) * cosA + rotationCenterY;
if (i == 0)
path.startNewSubPath(newX, newY);
else
path.lineTo(newX, newY);
}
path.closeSubPath();
return path;
}
The pathArray is a 2D array of x,y points
i.e.
[[x,y],[x,y] etc..]
@d-healey I'm imagining that you answered that question before I even submitted it! Lightning fast response.
Well, so far I have found that if I do not open the included .js file in a tab inside HISE, it seems I can work on changes inside VS Code and they will be retained when I compile in HISE. I don't know much about the caching system, so I can't guarantee this will continue to work, or work inside other environments. I am keeping a backup copy of the changes at every step at the moment just to keep an eye on it.
@d-healey I'm finding that HISE sometimes does take the changes made in VS Code at other times not. At this point in time I have not figured out the conditions which cause this however. I would like to continue editing in VS Code and then check out the changes by compiling in HISE. Seems kind of silly to cut and paste the entire script over to HISE no?
I am having issues that when I edit .js include files in VS Code, then hit "compile" in HISE, the .js files sometimes revert their changes, and I'm guessing this is from some cached version of the file in HISE which takes precedence. Is there some way to avoid this problem? Is there a setting to change or a workflow to adopt that will keep the changes made to the .js in VS Code?
@Christoph-Hart said in Looking into the hise_documentation on GitHub- any reason why a majority of the markdown files are empty for the Scripting API?:
Aw poor @VirtualVirgin that was half an hour that you'll never get back
"Aw poor @VirtualVirgin that was half an hour that you'll never get back"
No worries! Was learning how to generate markdown files from text.
I made text files of all of the classes in the API, then ran a python script to transform it to markdown. Just a learning experience. I then went on the generate JSON for the API with the following schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Scripting API Method",
"type": "object",
"properties": {
"class": {
"type": "string",
"description": "The class this method belongs to."
},
"method": {
"type": "string",
"description": "The method name."
},
"description": {
"type": "string",
"description": "A description of what the method does."
},
"syntax": {
"type": "string",
"description": "The usage syntax string for the method."
},
"parameters": {
"type": "array",
"description": "List of method parameters.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The parameter name."
},
"type": {
"type": "string",
"description": "The parameter type."
},
"optional": {
"type": "boolean",
"description": "Whether the parameter is optional."
},
"description": {
"type": "string",
"description": "A description of the parameter."
}
},
"required": ["name", "type", "optional", "description"],
"additionalProperties": false
}
},
"returns": {
"type": "string",
"description": "The return type of the method."
},
"examples": {
"type": "array",
"description": "Code examples demonstrating usage.",
"items": {
"type": "string"
}
}
},
"required": [
"class",
"method",
"description",
"syntax",
"parameters",
"returns"
],
"additionalProperties": false
}
Which makes this for example:
[
{
"class": "Array",
"method": "clear",
"description": "Clears the array.",
"syntax": "Array.clear()",
"parameters": [],
"returns": "",
"examples": [
"const var arr = []; // Declare an array\n\n// preallocate 10 elements, do this if you\n// know how many elements you are about to insert\narr.reserve(10); \n\nfor(i = 0; i < 10; i++)\n{\n\t// Add an element to the end of the array\n\tarr.push(Math.randInt(0, 1000));\n}\n\nConsole.print(trace(arr)); // [ 523, 5, 76, 345, 765, 45, 977, 223, 44, 54]\n\narr.clear();\n\nConsole.print(trace(arr)); // []"
]
},
{
"class": "Array",
"method": "clone",
"description": "Creates a deep copy of the array.",
"syntax": "Array.clone()",
"parameters": [],
"returns": "A deep copy of the array.",
"examples": [
"const arr1 = [0, 1];\n\nvar arr2 = arr1;\n\n// Changing any element in arr2 will also change it in arr1\narr2[0] = 22;\nConsole.print(trace(arr1)); // [22, 1]\n\n// Reset the element 0 back to 0\narr1[0] = 0;\n\n// Cloning the array creates a new dataset in memory, separate from the original array\narr2 = arr1.clone();\nConsole.print(trace(arr1));\narr2[0] = 22;\nConsole.print(trace(arr2));"
]
},
I'm sure this is all elementary for you and David, but I'm just learning how to do some these data formats and transformations with parsers etc.
@d-healey Just that if the markdown files for the Documentation on GitHub has mostly blank files, maybe these are useful? Or maybe I am not understanding the purpose of the markdown files on the GitHub site.
@d-healey I hope so! That is what I made them from. I just wanted to be able to use the Scripting API in markdown format, but your response makes me think that I am lacking context or missing something obvious?
Ok, the uploads doesn't like .md format so I just put it in a text file:
@Christoph-Hart I put the Scripting API into markdown files with on file per class. Would that be useful for the Git Docs? Here is an example:
The LicenseUnlocker
will aid you in managing HISEs keyfile and unlocking system.You can create an Unlocker
object with Engine.createLicenseUnlocker()
.
const var Unlocker = Engine.createLicenseUnlocker();
Checks if the unlocker's license system has an expiration date.
Unlocker.canExpire()
If the unlocker has an expiration date, it will check it against the RSA encoded time string from the server.
Unlocker.checkExpirationData( String encodedTimeString)
If you use the MuseHub SDK this will try to activate the plugin using their SDK.
Unlocker.checkMuseHub(var resultCallback)
Checks if the string contains the given substring.
Unlocker.contains(String otherString)
Returns the license key file as File object.
Unlocker.getLicenseKeyFile()
Returns the machine ID that is encoded into the license file. This does not look in the encrypted blob, but just parses the header string.
Unlocker.getRegisteredMachineId()
Returns the user email that was used for the registration.
Unlocker.getUserEmail()
Checks if the registration went OK.
Unlocker.isUnlocked()
Checks if the possibleKeyData might contain a key file.
Unlocker.isValidKeyFile(var possibleKeyData)
Checks whether the key file exists.
Unlocker.keyFileExists()
This checks if there is a key file and applies it.
Unlocker.loadKeyFile()
Sets a function that performs a product name check and expects to return true or false for a match.
Unlocker.setProductCheckFunction(var f)
Writes the key data to the location.
Unlocker.writeKeyFile( String keyData)
I am wondering what happened with the .md files here.
https://github.com/christophhart/hise_documentation/tree/master/scripting/scripting-api
I am supposing that an automated process to generate the files is leaving most of them blank?
I would like to know about this as well. To my knowledge I have not seen a commercial MIDI plugin that can route directly to and from MIDI ports/drivers, but I do remember that the two MIDI plugins here called "midiIn" and "midiOut" attempt to do this:
https://github.com/sleiner/pizmidi/tree/main/pizjuce
Some of the plugins are buggy, so I do not know if they work or not.
I think some of the challenge on Windows is that host apps can gain exclusive control over a port, and I know DAWs like Cubase/Nuendo are aggressive in this regard, but there are utilities to multiply port connections (such as MIDI-OX, Virtual Cable or Bidule), and therefore making it possible for multiple hosts to access the same MIDI input/output.
I would love it if you found a way to do this in a plugin. On an app I believe you should just be able to have access to the output ports from any of the MIDI devices as long as it is not in use by another app (I think it is first-come-first-serve in that regard).