diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 72fb58f6..789938d3 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -64,7 +64,7 @@ function getDevice(output, outputs) { } // Pattern.prototype.midi = function (output: string | number, channel = 1) { -Pattern.prototype.midi = function (output, channel = 1) { +Pattern.prototype.midi = function (output) { if (!supportsMidi()) { throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`); } @@ -109,21 +109,27 @@ Pattern.prototype.midi = function (output, channel = 1) { time = time * 1000 + timingOffset; // destructure value - const { note, nrpnn, nrpv, ccn, ccv } = hap.value; + const { note, nrpnn, nrpv, ccn, ccv, midichan = 1 } = hap.value; const velocity = hap.context?.velocity ?? 0.9; // TODO: refactor velocity const duration = hap.duration.valueOf() * 1000 - 5; if (note) { const midiNumber = toMidi(note); - console.log('midi number', midiNumber); - device.playNote(midiNumber, channel, { + device.playNote(midiNumber, midichan, { time, duration, attack: velocity, }); } - if (ccn && ccv) { - device.sendControlChange(ccn, ccv, channel, { time }); + if (ccv && ccn) { + if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) { + throw new Error('expected ccv to be a number between 0 and 1'); + } + if (!['string', 'number'].includes(typeof ccn)) { + throw new Error('expected ccn to be a number or a string'); + } + const scaled = Math.round(ccv * 127); + device.sendControlChange(ccn, scaled, midichan, { time }); } }); }; diff --git a/website/src/pages/learn/input-output.mdx b/website/src/pages/learn/input-output.mdx index 4688cf25..e4c2a63f 100644 --- a/website/src/pages/learn/input-output.mdx +++ b/website/src/pages/learn/input-output.mdx @@ -22,12 +22,35 @@ If no outputName is given, it uses the first midi output it finds. ".voicings('lefthand'), "") + tune={`stack("".voicings('lefthand'), "").note() .midi()`} /> In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".` +## midichan(number) + +Selects the MIDI channel to use. If not used, `.midi` will use channel 1 by default. + +## ccn && ccv + +- `ccn` sets the cc number. Depends on your synths midi mapping +- `ccv` sets the cc value. normalized from 0 to 1. + + + +In the above snippet, `ccn` is set to 74, which is the filter cutoff for many synths. `ccv` is controlled by a saw pattern. +Having everything in one pattern, the `ccv` pattern will be aligned to the note pattern, because the structure comes from the left by default. +But you can also control cc messages separately like this: + + + # SuperDirt API In mainline tidal, the actual sound is generated via [SuperDirt](https://github.com/musikinformatik/SuperDirt/), which runs inside SuperCollider.