Closed yaxu closed 6 years ago
sure, that would be quite easy to do this. I don't use midi devices myself, so the hardest bit would be to know what is needed on the midi side. Maybe @lennart would like to join?
I think the main thing to watch out for would be that there are a few Tidal->MIDI translation quirks that are currently handled in tidal-midi that SuperDirt would have to pick up. Mostly the stuff around translating duration-related Tidal params (sustain
, dur
, legato
) into timing for Note On/Note Off messages.
It would be very nice if the parameter->MIDI CC mapping necessary to add support for this/that hardware MIDI synth could be done on the fly in SuperDirt.
Perhaps for most cases simply being able to specify ccs by number rather than assigned name is enough, e.g. d1 $ n "c e f g" # s "m1" # cc3 sine # cc42 0.5
to send those notes to the first midi channel, with values sent for cc number 3 and 42. Then people can make aliases easily enough if they want to e.g. let wackymodulation = cc42
The standard way to do it in sclang is that you specify a midicmd
as a parameter (e.g. # midicmd polyTouch
), and the related arguments, e.g. # chan 2 # polyTouch 34
. The midinote
is automatically retrieved from freq
or note
, and amp
(velocity) is also derived from what we already have.
Here is a list of supported parameters and their arguments (I haven't tested them myself):
polyTouch: chan, midinote, polyTouch
program: chan, progNum
bend: chan, val
sysex: uid, array
noteOn: chan, midinote, amp
allNotesOff: chan
smpte: frames, seconds, minutes, hours, frameRate
songPtr: songPtr
control: chan, ctlNum, control
touch: chan, val
noteOff: chan, midinote, amp
Do you mean that this will already work from tidal @telephon ?
well, we need a little bit of tweaking, not too much. I haven't been able to run tidal for a while, some mess-up with haskell and atom maybe. Right now I have no time to look at it.
Can you try the following? Run this bit in sclang and see if you can make tidal to send the parameters above.
~dirt.soundLibrary.addSynth(\midi,
(play: {
"testing-----".postln;
currentEnvironment.postln
})
);
you should be able to call this event via tidal if you use sound "midi"
. The other parameters are formatted like I wrote above.
If this works, I can do the other end ...
@telephon I don't seem to have ~dirt.soundLibrary
:
ERROR: Message 'soundLibrary' not understood.
RECEIVER:
Instance of SuperDirt { (0xb5c1bc8, gc=D8, fmt=00, flg=00, set=04)
instance variables [15]
numChannels : Integer 2
server : instance of Server (0x2b68318, size=32, set=5)
buffers : instance of Event (0x18fdbde8, size=5, set=3)
vowels : instance of Event (0x12b60868, size=5, set=3)
orbits : instance of Array (0x11b5c988, size=5, set=3)
modules : instance of Array (0xb282238, size=13, set=4)
port : Integer 57120
senderAddr : instance of NetAddr (0xefd0a28, size=4, set=2)
replyAddr : nil
netResponders : instance of List (0xec07ef8, size=1, set=2)
fileExtensions : instance of Array (0x2cb6ac0, size=4, set=2)
receiveAction : nil
warnOutOfOrbit : true
verbose : false
maxLatency : Integer 42
}
ARGS:
CALL STACK:
DoesNotUnderstandError:reportError 0x11f92f18
arg this = <instance of DoesNotUnderstandError>
Nil:handleError 0x11f92ba8
arg this = nil
arg error = <instance of DoesNotUnderstandError>
Thread:handleError 0x11f922b8
arg this = <instance of Thread>
arg error = <instance of DoesNotUnderstandError>
Object:throw 0x11f91f48
arg this = <instance of DoesNotUnderstandError>
Object:doesNotUnderstand 0x11f8fe48
arg this = <instance of SuperDirt>
arg selector = 'soundLibrary'
arg args = [*0]
< closed FunctionDef > (no arguments or variables)
Interpreter:interpretPrintCmdLine 0xcc152b8
arg this = <instance of Interpreter>
var res = nil
var func = <instance of Function>
var code = " ~dirt.soundLibrary.addSynth..."
var doc = nil
var ideClass = <instance of Meta_ScIDE>
Process:interpretPrintCmdLine 0x11f6e448
arg this = <instance of Main>
^^ The preceding error dump is for ERROR: Message 'soundLibrary' not understood.
Ah I switched to 1.0-dev and now it works
testing-----
( 'length': 1, 'speed': 1, 'amp': 0.4, 'sustain': 0.999,
'pan': 0, 'fadeInTime': 0, 's': midi, 'cycle': 101269, 'fadeTime': 0.001,
'cps': 1, 'delta': 1, 'endSpeed': 1, 'note': 0, 'freq': an UnaryOpFunction,
'latency': 0.2949357600339, 'delayAmp': 0 )
(when I switch to 1.0-dev git tells me I'm 'in 'detached HEAD' state', slightly alarming!)
probably because of these git refs/tags, I keep having problems with them. I'll have to understand better how to use them. https://git-scm.com/book/en/v2/Git-Internals-Git-References
@yaxu here is a first test, try with your mididevice if you get note on and off
(
// substitute your own device here
MIDIClient.init;
~midiOut = MIDIOut.newByName("FastLane USB", "Port A");
~midiOut.latency_(~dirt.server.latency);
)
(
var p = (
type:\midi,
midicmd: \noteOn,
chan: 0,
midiout: ~midiOut
);
~dirt.soundLibrary.addSynth(\midi,
(play: {
var e = (note: ~note, sustain: ~sustain.value);
e.proto = p;
e.play;
})
);
)
d1 $ sound "midi*3" # n "5 7 1"
Yes this works well @telephon! I just had to change the latency to ~midiOut.latency_(0.4);
, to get it to match with superdirt sample playback. I can see the noteoffs going out, and they reflect the legato/sustain parameter correctly.
I've added a bit of code here, try and see if that is what we need. The default parameter names for midi in sclang I personally find a bit intrusive, but I left them for now.
https://github.com/musikinformatik/SuperDirt/commit/3f6351e29ee0a86506e1573c041387fa9e38a32d
@yaxu, if you think it should be easier to configure, I could wrap this script into a method, so given that you have a midi-device name, you could write e.g. ~dirt.initMIDI(\midi, "device name", (chan: 5))
. Just let me know.
Thanks @telephon, I'm away from any midi devices for a couple of nights but this looks great. I think an initMIDI
method would be very handy. I wonder if it would be better to make chan
count from 1.
Being able to send MIDI clock start/tick/stop could be useful too.
Being able to send MIDI clock start/tick/stop could be useful too.
Probably works already, you just send # midicmd "start"
etc.
I wonder if it would be better to make chan count from 1.
The protocol should be transparent, just pass through to the end what is there. What is the midi default channel?
Righto. I believe the first channel (0) is default, with the tenth channel (9) used for percussion (in general midi at least).
btw I think that the following are also automatically supported (I haven't tested any of this):
midiClock {
this.write(1, 16rF0, 16r08);
}
start {
this.write(1, 16rF0, 16r0A);
}
continue {
this.write(1, 16rF0, 16r0B);
}
stop {
this.write(1, 16rF0, 16r0C);
}
reset {
this.write(1, 16rF0, 16r0F);
}
to be used from tidal by ways of:
sound "midi" # midicmd "start"
sound "midi" # midicmd "stop"
sound "midi" # midicmd "continue"
etc.
I tested this out today on Win 10 w SuperDirt 3.8 and cabal Tidal 0.96. I updated my Quark's list through the GUI and grabbed the Version called "push" because I noticed it had the tidal-midi.scd in the scripts folder so I'm guessing that's the latest. I'm using @telephon 's script without modification, except the midi port ,that he linked here: https://github.com/musikinformatik/SuperDirt/commit/3f6351e29ee0a86506e1573c041387fa9e38a32d
and I also tried the tidal-midi.scd included with the "push" release.
I'm getting unexpected rhythmic patterns. In the two videos below, you'll see I have three notes per cycle playing, but something on the SuperDirt/Supercollider side is spacing them out in an unexpected way.
https://drive.google.com/open?id=1R2tJOZ7o5TzL5iAqNMH_XTlj5jwCwQ4x
https://drive.google.com/open?id=1CwHhz7cTyJyyqU_P1Ww5ffE-k54gCDFo
d1 $ n "c2 c3 c5" # sound "midi"
is rhythmically messed up
m1 $ n "c2 c3 c5"
works as intended
Because of this I'm fairly certain it has nothing to do with my loopMIDI/Ableton setup.
thank you, @lagauche. I might have fixed it now, try updating the quark.
The new version of the patch should start with
// version 0.2
What file would I find // version 0.2
in to make sure I'm using the latest?
I removed my SuperDirt folder, checked for updates and grabbed "push" again 5 minutes ago.
Since I'm getting new errors, I think I might have it, but I want to make sure. Unfortunately I'm still getting weird funky rhythmic patterns like in the video I posted.
It's almost as if SuperDirt is applying some kind of Euclidian rhythm function.
These are the new console errors I get after SuperDirt successfully boots:
CheckBadValues: unknown found in Synth 1016, ID 0
CheckBadValues: unknown found in Synth 1016, ID 0
CheckBadValues: unknown found in Synth 1012, ID 0
CheckBadValues: unknown found in Synth 1012, ID 0
CheckBadValues: unknown found in Synth 1008, ID 0
CheckBadValues: unknown found in Synth 1008, ID 0
CheckBadValues: unknown found in Synth 1004, ID 0
CheckBadValues: unknown found in Synth 1004, ID 0
CheckBadValues: unknown found in Synth 1000, ID 0
CheckBadValues: unknown found in Synth 1000, ID 0
CheckBadValues: unknown found in Synth 1044, ID 0
CheckBadValues: unknown found in Synth 1044, ID 0
CheckBadValues: unknown found in Synth 1040, ID 0
CheckBadValues: unknown found in Synth 1040, ID 0
CheckBadValues: unknown found in Synth 1036, ID 0
CheckBadValues: unknown found in Synth 1036, ID 0
And I get errors when playing SuperDirt samples like "bd"
(the sound still plays fine):
CheckBadValues: normal found in Synth 1000, ID 0 (previous 5650154 values were unknown)
CheckBadValues: normal found in Synth 1000, ID 0 (previous 5650154 values were unknown)
CheckBadValues: unknown found in Synth 1000, ID 0 (previous 12553 values were normal)
CheckBadValues: unknown found in Synth 1000, ID 0 (previous 12553 values were normal)
I get no console errors when evaluating the new midi feature such as in:
d1 $ n "c2 c3 c5 c2 c3" # sound "midi"
But it sounds like this: https://drive.google.com/open?id=1GqHUrxpsgnTbjwEy9xjySTjkKXqtpxo9
The timing is rhythmically/mathematically off.
When I change SuperDirt Version to "Latest" these errors go away, but of course I can't eval the tidal-midi.scd script.
P.S. I just deleted my second post regarding some errors on evaling tidal-midi.scd but it turns out I just needed to boot SuperDirt first. Everything above is still happening though.
can you try and use the version called "1.0-alpha"? I don't know what "push" is …
… ah I see, "push" is a tag that shouldn't be there.
I'm not sure if the versions that are displayed in the GUI are correct. If in doubt, just download the 1.0-dev branch from here.
I'm using this to get sync with the sequencer on this volca keys synth. Works well!
d1 $ midicmd "[start/4,midiClock*24]" # s "midi" # nudge 0.1
@telephon I downloaded the 1.0dev from github, put it into my downloaded-quarks folder, opened the GUI and ticked the checkbox to install. I didn't realize it was that easy.
I believe you fixed it! Timing seems accurate now and it's also syncing up with normal Tidal without any changes to the default latency setting 0.0.
I'm still getting these errors over and over though:
CheckBadValues: unknown found in Synth 1000, ID 0 (previous 12553 values were normal)
CheckBadValues: normal found in Synth 1000, ID 0 (previous 10967 values were unknown)
I would really love to test Control Change (e.g. "CC") params once they are supported in this.
given this proposed code:
~dirt.initMIDI(\midi, "device name", (chan: 5))
How would I set up multiple synths on multiple channels? e.g. synth1 on channel 1, synth2 on channel 15, and synth3 on channel 16? How would they then be played?
@kindohm I have multiple channels working using the tidal-midi.scd provided in the SuperDirt 1.0dev and I'm playing them with all the standard Tidal functions as long as I have # s "midi"
Set your device here (taken from the provided script): ~midiOut = MIDIOut.newByName("loopMIDI Port 2", "loopMIDI Port 2");
There's no need to specify a channel since we'll do that in Tidal.
eval (midichan, midichan_p) = pF "midichan" (Nothing)
in Haskell
then do # s "midi" # midichan n
. This seems to work perfectly.
I still have no idea how to add multiple ports/devices in Supercollider though and then access them in Tidal. Hopefully it's as simple as duplicating ~midiOut
with a new port/device and then creating another function like midichan
to select it. Anyone have any ideas?
Support for multiple devices would be an important feature for me. Would be really great to have that.
@kindohm
with the current version, you can try this the following way:
(
~midiOut1 = MIDIOut.newByName("loopMIDI Port 2", "loopMIDI Port 2");
~midiOut2 = MIDIOut.newByName("loopMIDI Port 2", "loopMIDI Port 2"); // replace by your devices
var addMidi = { |name, device|
~dirt.soundLibrary.addSynth(name,
(
play: {
var e = (
type: \midi,
midiout: device,
lag: ~lag + ~latency, // in the midi event, lag is used as latency
note: ~note ? 0,
sustain: ~sustain.value,
midicmd: ~midicmd ? \noteOn,
chan: ~midichan ? 0,
);
e.play;
}
)
);
};
addMidi.(\midi1, ~midiOut1);
addMidi.(\midi2, ~midiOut2);
)
This looks really promising, thanks for giving it a go @telephon!
I've just got everything hooked up and going to give it a bit of a stress test. I have one question now though, I don't quite understand the final block of the tidal-midi.scd
script:
// the following midicmds and their parameters are then supported
// the usual note/freq and legato/sustain parameters are taken into account
polyTouch: chan, midinote, polyTouch
program: chan, progNum
bend: chan, val
sysex: uid, array
noteOn: chan, midinote, amp // default
allNotesOff: chan
smpte: frames, seconds, minutes, hours, frameRate
songPtr: songPtr
control: chan, ctlNum, control
touch: chan, val
noteOff: chan, midinote, amp // sent automatically, after sustain.
Currently I'm just doing things like d1 $ n "0 1 2 3" # s "midi" # midichan "0"
, but how would I use e.g. amp
(which I assume is note on velocity)? I can't do # amp 0.5
as that is not listed in the script as required for usage in Tidal. Same goes for the rest of these params. Do I need to define them in Tidal too?
And actually one more q: why does chan
not work but midichan
does?
Very exciting!
Still trying to understand how to send the additional params
If I do d1 $ midicmd "control" # s "midi"
I get
ERROR: Message 'asInteger' not understood.
RECEIVER:
nil
ARGS:
CALL STACK:
DoesNotUnderstandError:reportError 0x11b549fd8
arg this = <instance of DoesNotUnderstandError>
Nil:handleError 0x11b56f828
arg this = nil
arg error = <instance of DoesNotUnderstandError>
Thread:handleError 0x1197668e8
arg this = <instance of Thread>
arg error = <instance of DoesNotUnderstandError>
Object:throw 0x11ea14228
arg this = <instance of DoesNotUnderstandError>
Object:doesNotUnderstand 0x11b540388
arg this = nil
arg selector = 'asInteger'
arg args = [*0]
MIDIOut:control 0x11a804778
arg this = <instance of MIDIOut>
arg chan = 0
arg ctlNum = nil
arg val = 125
Function:awake 0x11a6914f8
arg this = <instance of Function>
arg beats = 4088.1130239488
arg seconds = 4088.1130239488
arg clock = <instance of Meta_SystemClock>
var time = 4088.1130239488
^^ The preceding error dump is for ERROR: Message 'asInteger' not understood.
RECEIVER: nil
If I do d1 $ midicmd "program" # midichan 1 # progNum 5 # s "midi"
I get this (time/source/message/channel/data) - the progNum
doesn't seem to work
23:19:03.350 To tidal Program 2 1
23:19:04.350 From tidal Program 2 1
If I define (amp, amp_p) = pF "amp" (Nothing)
and then do d1 $ n "0 1 2 3" # midicmd "noteOn" # midichan 0 # amp "0.8" # s "midi"
the amp
param doesn't have an effect.
For a stress test I did this running MIDI into 8 software instruments, no timing problems whatsoever
d1 $ fast 16 $ n (scaleP "major" (run 16)) # s "midi" # midichan "[0 1 2 3 4 5 6 7]"
@telephon any idea why I'm getting errors like these CheckBadValues: unknown found in Synth 1176, ID 0
when I boot SuperDirt and when playing samples? Everything seems to be working nonetheless.
Could it have anything to do with the fact that I downloaded the 1.0dev from github but then I hit install in the Quarks gui? I'm doubting it because I also tried re-installing the 1.0 alpha from the gui and same error persists.
@jarmitage
I don't quite understand the final block of the tidal-midi.scd script
this is not code, just documentation
I can't do # amp 0.5 as that is not listed in the script as required for usage in Tidal.
should work despite not being listed. not sure why it doesn't.
And actually one more q: why does chan not work but midichan does?
it's just a matter of the bindings in tidal. I thought midichan is better, but, well, we'll better discuss naming in en extra step.
If I do
d1 $ midicmd "control" # s "midi"
I getERROR: Message 'asInteger' not understood.
ah, maybe a default parameter not defined, probably ctlNum
.
@lagauche
what is the tidal code that produces the bad values?
@telephon For example the most basic tidal code causes it to occur every time a sample sound plays :
d1 $ s "bd"
What is the code I need to add to the midi
synth in order for it to receive Control Change params? I want to send random CC values to control #100 on my synth:
d1 $ sound "midi" # n "0" # midichan "0" # ctlNum "100" # control (rand)
^^ not even sure that's correct.
~dirt.soundLibrary.addSynth(\midi,
(
play: {
var e = (
note: ~note ? 0,
sustain: ~sustain.value,
midicmd: ~midicmd ? \noteOn,
chan: ~midichan ? 0,
);
e.proto = p;
e.play;
}
)
);
That should be enough Mike, along with the initialisation stuff above https://github.com/musikinformatik/SuperDirt/blob/1.0-dev/scripts/tidal-midi.scd
I'm not sure what the range for these parameters is offhand. You might want to send to ctlNum 99
, if it starts counting at 0, or on the other hand it might be scaled to 0 .. 1
, or (maybe more likely) control
might be scaled to 0 .. 127
.
I can't get it to work. I've tried indexing from both 0 and 1 for the ctlNum
value, and have tried scaling values from both 0..1
and 0..127
. I also can't get the amp
param to work either, as @jarmitage also reported (maybe it's related?).
you need to specify the midicmd, in this case # midicmd "control"
Can you check if this works for you now?
I've wrapped the MIDI function into a method.
https://github.com/musikinformatik/SuperDirt/commit/d0e3786e6c83b969c31dbbd1ecedd9e7d52a09a1
Yes that works Shall I release a new tidal version with the parameters available?
I'm still not seeing control changes:
d1 $ sound "midi*4" # midinote "0 1 2 3" # midichan "0" # ctlNum "35" # midicmd "[control, noteOn]" # control (scale 0 120 $ sine)
I've tried zero-indexing ctlNum
and have also tried scaling the control values from 0..1
. I also tried swapping ctlNum
for control
, thinking I might have them backwards, but that didn't help either.
for debugging, can you try and run this:
(
fork {
20.do {
~midiOut.control(chan: 0, ctlNum:35, val:126.rand);
0.5.wait;
}
}
)
@yaxu when you say this is working for you, which parts are working, and can you post Tidal code? I couldn't get anything other than note and channel data working.
@jarmitage I inly got note and midi clock data working. I tried cc last night, but with a midi snooper (no synth to hand). I could see control messages but couldn't see control values changing.
I’m not sure it’s worth adding to a Tidal release until CCs and the parameters listed above are confirmed to be working. Also the suggested syntax doesn’t feel very Tidal-y to me somehow.
... Or maybe it is worth getting a minimal thing out there 😅
Currently tidal-midi works separately from tidal, generally works well but needs some bugfixes as it can glitch and go out of time. Would it be easy to send MIDI via superdirt instead? That could have some advantages in terms of getting accurate sync, and taking advantage of the well developed+used MIDI support there. Perhaps it wouldn't be a lot of code to achieve the basics?