ncassetta / NiCMidi

A MIDI C++ library with objects for reading, writing, playing, editing and recording midi files
GNU Lesser General Public License v3.0
2 stars 1 forks source link

Question about your code #1

Closed goofy2k closed 2 years ago

goofy2k commented 2 years ago

Hi @ncassetta , I have been working on using jdksmidi in a soundboard project. I have difficulties in implementing the original jdksmidi code, so I have been studying it.

Maybe you can help me a bit.....

In the jdksmidi code, incoming messages are stored in a circular in_queue (MIDIQueue). I expect that, e.g. when recording, these messages must be transferred to the actual track that can be played. In jdksmidi I did not discover where this is done. I would expect that the messages received must be put in order of time that they should be played. In jdksmidi track.cpp/.h I see routines for sorting a track. There is NO routine for inserting (transfering messages from the in_queue to a track). I would think that this repetitive task should be executed in a MIDITick routine.

What are your thoughts about this? How is this implemented in your code.

Thanks

goofy2k commented 2 years ago

I now see that in recorder.cpp you made a remark: // TODO: perhaps it is possible to write a Sequencer::InsertEvent() method

This probably relates to my question.

The point is that while e.g. playing a loop and at the same time feeding MIDI events to the system you need to have a way to safely enter them into a track while playing goes on. I think jdksmidi solves this by adding received events in in_queue. The only two time-critical thing that you have to do fast are: providing a timestamp of the time of receipt and sending to an output (if MIDI thru is enabled). Then, you have the complete length of the recording loop to insert the event into the right place in to a track. I did not find this action in the jdksmidi code.

ncassetta commented 2 years ago

Hi @goofy2k, jdksmidi is very old code, I ported it to NiCMidi. However, as you have seen, it did not allow the user to record: the MIDIDriver class could only receive messages and put them in its internal queue, not in a MIDITrack. In NiCMidi I organized things like this:

It must be associated with a MIDISequencer (given in the constructor). As I wrote in a comment, I initially tried to add messages directly to the sequencer tracks, but this gave trouble because it changed the tracks while they were playing. So I had to copy the sequencer tracks into the recorder (in the Start method), write to them (in the TickProc method executed every tick of the timer) and then copy them back into the sequencer (in the Stop method). The MIDIRecorder object is still rather basic and does not allow, for example, to record in loops. You can examine the test_recorder.cpp sample file in the examples folder to have an idea of the MIDIRecorder usage.

goofy2k commented 2 years ago

Hi Nicola, thanks for your explanation.

I have a (hobby) project where I want to use MIDI to control a TTGO TAudio (ESP32) board that runs a Faust synthesizer. I now feed individual MIDI messages created on a second ESP32 to the sound board over bluetooth (Nimble). This works fine (currently only keyOn / keyOff are interpreted by the sound board firmware). I also implemented basic recording in a loop. Now I want to add more elaborate sequencing / recording capabilities to the second ESP32 board. I tried jdksmidi but I found it very complex to implement.

So... Google led me to your lib. I like it very much because the code is much more modern C++.

I now add it to my project as a component (I use ESP-IDF). Still having some trouble with CMake etc......

The next step is to connect to my input and output "ports". When I have that in place, the work can start.

Output is BLE (NimBLE). I have to discover how to let that cooperate with your Openport command. Input (for the "sequencer)" is (for now) MQTT delivering key-presses. Also that has to be connected. Any suggestions for opening non-standard ports? I somehow have to use callback to my communication routines, I suppose. I already read something about OpenVirtualport in RtMidi....

In the mean time I try to do the first calls to your lib: added your example that creates three MIDIMessage instances to my app, but still get some complaints from CMake.

Greetings, Fred

Op za 20 nov. 2021 09:14 schreef Nicola Cassetta @.***>:

Hi @ goofy2k, jdksmidi is very old code, I ported it to NiCMidi. However, as you have seen, it did not allow the user to record: the MIDIDriver class could only receive messages and put them in its internal queue, not in a MIDITrack. In NiCMidi I organized things like this:

  • the MIDIInDriver class manages a queue of MIDIRawMessage objects coming from the hardware port: these are structs composed of
    • a MIDIMessage object
    • a timestamp (in msecs)
    • the number of the MIDI in port.
  • the MIDIRecorder class gets these messages, transforms them into MIDITimedMessage objects (assigning them a time in MIDI ticks) and puts them into MIDITrack objects.

It must be associated with a MIDISequencer (given in the constructor). As I wrote in a comment, I initially tried to add messages directly to the sequencer tracks, but this gave trouble because it changed the tracks while they were playing. So I had to copy the sequencer tracks into the recorder (in the Start method), write to them (in the TickProc method executed every tick of the timer) and then copy them back into the sequencer (in the Stop method). The MIDIRecorder object is still rather basic and does not allow, for example, to record in loops. You can examine the test_recorder.cpp sample file in the examples folder to have an idea of the MIDIRecorder usage.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/ncassetta/NiCMidi/issues/1#issuecomment-974614229, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGII3TTRE5QLD4CV6VJPE5TUM5KHJANCNFSM5IMFSN5Q .

goofy2k commented 2 years ago

At first I tried to add recording / looping capabilities to the board that runs the digital sound processing, but two of those time-critical tasks is too much for a single ESP32. So I decided to divide the tasks over two boards and connect them via Bluetooth.

goofy2k commented 2 years ago

I made the repo for my project public! You can have a look at: https://github.com/goofy2k/MIDI-sequencer

goofy2k commented 2 years ago

The repo contains both apps:

ncassetta commented 2 years ago

@goofy2k jdksmidi only had hardware support for Windows, not even Linux. So in NiCMidi I used RtMidi for communications with hardware, focusing on sequencing/recording and loading/editing/saving of MIDIFiles. In NiCMidi the communications with the hardware are managed by the MIDIOutDriver and MIDIInDriver classes: the first has an OutputMessage method (which in turn calls HardwareMsgOut) to send a message to an Out port, while the second has a HardwareMsgIn callback activated when a Midi message comes from an In port. More advanced classes automatically call these methods every tick of the main timer to get playing or recording. To interface with non-standard ports you probably have to study RtMidi, I don't have much experience with hardware.

goofy2k commented 2 years ago

Thanks. I have code for sending and receiving data to/from the BLE interface in place. I already contacted users of the RtMidi package about "glueing" that to a RtMidi port.

Another "issue" is your timer methods. I got some complaints from my compiler about that, specifically about using and/or or

. I now managed to include slightly adapted versions of your msg.h and track.h I to my code. I will first connect my incoming BLE messages with that and then switch to the timer methods. Fred Op zo 21 nov. 2021 16:16 schreef Nicola Cassetta ***@***.***>: > @goofy2k > jdksmidi only had hardware support for Windows, not even Linux. So in > NiCMidi I used RtMidi for communications with hardware, focusing on > sequencing/recording and loading/editing/saving of MIDIFiles. > In NiCMidi the communications with the hardware are managed by the > MIDIOutDriver and MIDIInDriver classes: the first has an OutputMessage > method (which in turn calls HardwareMsgOut) to send a message to an Out > port, while the second has a HardwareMsgIn callback activated when a Midi > message comes from an In port. More advanced classes automatically call > these methods every tick of the main timer to get playing or recording. > To interface with non-standard ports you probably have to study RtMidi, I > don't have much experience with hardware. > > — > You are receiving this because you were mentioned. > Reply to this email directly, view it on GitHub > , > or unsubscribe > > . >
ncassetta commented 2 years ago

@goofy2k What compiler are you using? Are you using the c++ 0x11 flag?

goofy2k commented 2 years ago

I use ESP-IDF from the command line. I tried to look up the compiler version, but did not succeed....

ESP-IDF promotes the use of freeRTOS for time critical applications. So I am not sure if your timer.h/.cpp is compliant with the system.

Op zo 21 nov. 2021 20:05 schreef Nicola Cassetta @.***>:

What compiler are you using? Are you using the c++ 0x11 flag?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ncassetta/NiCMidi/issues/1#issuecomment-974874752, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGII3TWDAA4BLBH32CLC5E3UNE7GHANCNFSM5IMFSN5Q .

goofy2k commented 2 years ago

For the MIDI input I see 2 options: 1) adaptation on the RtMidi side, using openVirtualPort 2) adaptation in your MIDIInput class in driver.h/.cpp

I'll do one of these options first.

After that I will investigate creation of a timer class based on freeRTOS.

Fred

goofy2k commented 2 years ago

In your docs the hierarchy of classes is shown. It is useful to know that a number of classes depend on a single MIDITickComponent. I miss information about how MIDITimer fits into this all. Is MIDITimer a base class for MIDITickComponent? And only for that?

A quick inspection of your code let's me think that only driver, manager and tick depend directly on timer. If it is I can try to design a freeRTOS based MIDITimer class without having to adapt the dependent classes.

goofy2k commented 2 years ago

Can run the example test_component example now (with MIDITickComponent, MIDIManager, MIDITimer). It still has a run-time error at stopping the component. I had to comment out MIDIIn, MIDIout reference. I'll probably wrap my MQTT (in) and BLE (out) code in these classes. Must see if I keep RtMidi. I guess that this causes overhead that is not required. ESP32 memory is limited :-) Everything available in my repo.....

ncassetta commented 2 years ago

No, the MIDITimer is a static class which only (when it is open) starts a background thread which executes its ThreadProc every 10 milliseconds. The MIDITick class has a callback which is called by the timer procedure, It doesn't interacts wit the MIDITimer and includes the header "timer.h" only for the definition of the type tMsecs. The MIDIInDriver uses the MIDITimer only in the static HardwareMsgIn method, for timestamp of the incoming messages

goofy2k commented 2 years ago

In the mean time I have this running! I have tested some of your examples succesfully. So, no need to switch to freeRTOS timers etc.

I bypassed the RtMidi output and directly write to my basic bluetooth output. With succes. To prevent that I have to hack your code in more places, I think that I will cast my bluetooth driver into a class that mimics RtMidi.

Because I am on an ESP32 it is hard to use the examples that depend on user input from the command line, so it is hard to test the sequencer and recording examples.

So now I have to decide:

OR

I would like to be able to record notes, while playing a loop. When this works nice, making the jump to building a GUI becomes more attractive...

Op wo 24 nov. 2021 23:22 schreef Nicola Cassetta @.***>:

No, the MIDITimer is a static class which only (when it is open) starts a background thread which executes its ThreadProc every 10 milliseconds. The MIDITick class has a callback which is called by the timer procedure, It doesn't interacts wit the MIDITimer and includes the header "timer.h" only for the definition of the type tMsecs. The MIDIInDriver uses the MIDITimer only in the static HardwareMsgIn method, for timestamp of the incoming messages

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ncassetta/NiCMidi/issues/1#issuecomment-978343732, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGII3TTFUB7PSYU2GDQ5GODUNVQSXANCNFSM5IMFSN5Q .

goofy2k commented 2 years ago

Making some progress :-) Trying to implement the test_recorder example . Discovering some potential bugs. See Issues.

goofy2k commented 2 years ago

just closing, to keep focus on the open issue, which is more a discussion thread :-)