Closed medfordengineering closed 4 years ago
Hello,
Is it possible to save the complete serial dump output from a Moppy a midi song file locally? Our goal is to run our six Moppy drive set up without requiring the Moppy Java interface. If we can store midi song files pre-processed by the Moppy Java interface then we can store them as playlist on our six Moppy drive set up. We tried to do this with a Saleae logic analyzer and with Eltima Serial Port Monitor but it seems the data was corrupted. Any suggestions would be appreciate.
Sam Christy Robotics Instructor Medford Vocational Technical High School https://mvthsengineering.design.blog/
With an second Arduino this is possible (if you have enough space in RAM/FLASH
So it depends on the length of the Songs, but i think the 4MB version of the ESP8266 is more than enough.
But there is also a beta-Version which Supports directMidi in. (You can find it in the Pull Requests) This could save some Space and there are existing projects which convert MIDI Files in Arduino-readable Format
The challenge will be less saving the serial dump to a file (some minor modifications to the Java code should allow you to write to a file instead of writing to the serial port), as it will be saving the timing information to the file and getting the Arduino to replay it in the correct time. The messages being passed to the serial port don't contain any timing information; that is, when a note starts a NoteOn message is sent, and then sometime later when it's time for that note to stop, a NoteOff message is sent. I haven't actually been able to think of a clean way to do compress that timing information down into a file for the Arduino to read that's any better than just having the Arduino read a MIDI file directly.
As @MagnusM1709 points out, he has a fork that adds MIDI support on the Arduino side of things for input. You'd need to find a way to run the MIDI sequence on the Arduino (maybe using this library?) and then convert the MIDI events to Moppy events (which @MagnusM1709 's code would be a good example of.
Thanks for the detailed response. Interesting. As I understand MIDI by definition contains timing information, so I am curious why you are not using it as part of the file sent from Moppy over serial.
Alternatively, is there a way to set up a simple command line version for Raspberry Pi that can simply be used to load/run specific MIDI files?
That's a good question. The MIDI file (by my understanding) does contain timing information in the form of timestamps for each event, and the MIDI sequencer (in this case Java's MIDI sequencer) plays back the MIDI file using these time stamps (essentially firing each event timestamp-milliseconds after the start of playback). The sequencer sends these MIDI events to a listener during playback, and the listener does something with them (converts them to audio if it's a synthesizer, or in Moppy's case converts them into MoppyMessages and sends them over the serial port).
Because the events from the sequencer are happening in real-time, the serial messages are being sent in real time, and the Arduino simply needs to act on them as quickly as possible. The latency introduced by the conversion of the MIDI events to MoppyMessages and their transmission over serial is very small (and more importantly consistent) so this doesn't pose any real playback issues.
The decision to decode the MIDI files on the PC instead of decoding them on the Arduino was motivated by a lack of processing power on the Arduino (I originally had many issues trying to decode MIDI and playback notes on the drives, though I think the code has improved to the point where this is more feasible now), and the ability to perform complicated mapping tasks on the PC to send notes to multiple Arduinos, Windows Synthesizer, other MIDI programs, &c.
It would take a bit of coding, but the MoppyLib Java library does most of the work and was designed with a command line application in mind for the future. You'd just need to create a command line Java program that takes a MIDI file (and drive configuration) options as arguments and calls MoppyLib to playback the file over serial.
Hello Again,
So with some extra time recently, I have been making some progress on creating my own software for running floppys and steppers from MIDI. My big question now is how do you deal with chords? Most of the MIDI files that I find have multiple notes playing at the same time in the same channel. I can imagine a few solutions, but was just wondering whether most people solve this by modifying the MIDI file or by creating a parser to move simultaneous notes to different motors. Presently, I can use command line tools (ttymidi) and python to open and stream MIDI files to the Arduino. At the Arduino end, I can easily convert notes to frequencies and play them. At this point, I am just missing the part about parsing the chords to multiple motors. Any suggestions would be appreciated.
Hope you and those in your world are safe.
Sam
On Thu, May 16, 2019 at 1:58 PM SammyIAm notifications@github.com wrote:
That's a good question. The MIDI file (by my understanding) does contain timing information in the form of timestamps for each event, and the MIDI sequencer (in this case Java's MIDI sequencer) plays back the MIDI file using these time stamps (essentially firing each event timestamp-milliseconds after the start of playback). The sequencer sends these MIDI events to a listener during playback, and the listener does something with them (converts them to audio if it's a synthesizer, or in Moppy's case converts them into MoppyMessages and sends them over the serial port).
Because the events from the sequencer are happening in real-time, the serial messages are being sent in real time, and the Arduino simply needs to act on them as quickly as possible. The latency introduced by the conversion of the MIDI events to MoppyMessages and their transmission over serial is very small (and more importantly consistent) so this doesn't pose any real playback issues.
The decision to decode the MIDI files on the PC instead of decoding them on the Arduino was motivated by a lack of processing power on the Arduino (I originally had many issues trying to decode MIDI and playback notes on the drives, though I think the code has improved to the point where this is more feasible now), and the ability to perform complicated mapping tasks on the PC to send notes to multiple Arduinos, Windows Synthesizer, other MIDI programs, &c.
It would take a bit of coding, but the MoppyLib Java library does most of the work and was designed with a command line application in mind for the future. You'd just need to create a command line Java program that takes a MIDI file (and drive configuration) options as arguments and calls MoppyLib to playback the file over serial.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/SammyIAm/Moppy2/issues/66?email_source=notifications&email_token=ACICJ64KMN7KYNAO6IGUBMDPVWODXA5CNFSM4HNDLDZKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODVSS7GQ#issuecomment-493170586, or mute the thread https://github.com/notifications/unsubscribe-auth/ACICJ6Z2QFC773NCWZDYR4DPVWODXANCNFSM4HNDLDZA .
-- Sam Christy Robotics Instructor Medford Vocational Technical High School 489 Winthrop St. Medford MA 02155 781-393-2457 https://mvthsengineering.design.blog/
Oh man, I'm sorry it took me so long to get to this. I just found it today on the second page of my unread inbox emails 😳.
I think there's probably at least 2.5 high-level strategies for dealing with chords:
1. Just ignore them; this is what Moppy does by default. The sequencer only processes one event at a time, so simultaneous events still get processed sequentially. Note On
events beyond the first one will get processed probably before the floppy drive even has a chance to start playing the first note. The biggest challenge with this strategy is that the notes in the chord may not all turn on and off simultaneously, so you have to make sure that if you get a Note Off
that you check and make sure it's actually for the note that the drive is currently playing, and not for an earlier note that's since been superseded.
2. Use more drives; Moppy2 has an option for this where it uses a round-robin algorithm to assign notes to drives irrespective of channel. As each event is processed, the next drive is used. I.e. the first note goes to drive 1, the second to drive 2, etc. This ensures most of the notes at least get played, but it can sound a bit weird as different drives grab different parts of the song.
Another option is to use a "stack" of drives for a single channel, so that if there's only one note playing, the first drive in the stack is the only one that plays. If two or more notes are on simultaneously, additional drives in the stack are activated so that they all get played. You'd probably need a stack of drives for each channel (unless you just ignore channels entirely, which is a valid option), but this would let you play well beyond 16 simultaneous notes if you had enough drives and I/O pins.
2.5 Use tracks instead of channels. My understanding is that "channels" in MIDI are intended to designate a voice, rather than separate out strings of notes. "Floppy Drive" would conceivably be a single channel, and you'd use a different channel for your scanner, a different channel for the tesla coil, etc. If you're not just using round robin or stack assignments though, you'll need some way to assign certain notes to certain drives, and I coopted channels for this purpose because the Java MIDI libraries don't provide any track information with MIDI events.
If your tools allow you to see which track an event is on, you could have an unlimited number of tracks, and assign a drive to each track rather than each channel. This doesn't fully solve your chord issue (since chords will likely be on the same track in the file as well), hence "2.5", but I figured I'd throw it out there as an alternative drive-assigning method in case that's helpful (maybe there's a tool that will break up tracks into multiple tracks where each track has one note, for example).
Good luck! Do you have a GitHub repo I can star to follow your progress? Would love to see what you come up with not limited by Java's libraries.
Thanks for the reply and detailed information. Now I am apologizing for not responding more promptly. I may have mentioned that I teach engineering at a vocational high school https://mvthsengineering.design.blog/. The past couple of months have been a bit hectic and I am just getting around to working on the MIDI project. Over the past couple of days, I was able to wire up an Arduino that can accept a standard MIDI file over serial and drive four stepper motors (I don't have the floppies at home) based on the notes in the file. The software is fairly rudimentary at this point. It takes advantage of a MIDI library https://github.com/FortySevenEffects/arduino_midi_library/ designed for Arduino which does almost all the work parsing notes, channels and tracks. I set up a 10us resolution interrupt to drive the four motors. I also converted each of 127 MIDI notes to a relative period in preprocessing to avoid taxing the Arduino with any heavy math.
I dealt with the problem of overlapping notes by simply requiring that all MIDI files are human preprocessed, so that all overlapping notes are in separate channels. It would be easy to convert this to tracks, but channels work for now. Also, as I began to work on the project, I began to think that processing the MIDI files into channels designed for steppers, or floppies, or relays could be fun and creative work. Note that the MIDI files I included came from another project which was designed for four steppers.
On the Linux side, I just use ttymidi https://launchpad.net/ttymidi which creates a midi serial port from a command line interface. At this point anything that can send a MIDI file works. I wrote a very simple python program using the Mido https://mido.readthedocs.io/en/latest/ library which does all the heavy lifting on the Linux side. I will probably have my students create a web interface, since we don't do much actual app design in my shop. One or two students will create the whole project next year. I just like to get a bit ahead of them (when I have the chance which is rare) so that I can be more helpful as I guide them through their own exploration.
Thanks again for your insights and inspiration. These are the kinds of projects that make my students excited to come to school everyday. Please share with me any thoughts. Here is a link https://github.com/medfordengineering/midi_stepper_driver to what I have on github so far.
Sam
On Tue, Apr 14, 2020 at 5:45 PM SammyIAm notifications@github.com wrote:
Oh man, I'm sorry it took me so long to get to this. I just found it today on the second page of my unread inbox emails 😳.
I think there's probably at least 2.5 high-level strategies for dealing with chords:
1. Just ignore them; this is what Moppy does by default. The sequencer only processes one event at a time, so simultaneous events still get processed sequentially. Note On events beyond the first one will get processed probably before the floppy drive even has a chance to start playing the first note. The biggest challenge with this strategy is that the notes in the chord may not all turn on and off simultaneously, so you have to make sure that if you get a Note Off that you check and make sure it's actually for the note that the drive is currently playing, and not for an earlier note that's since been superseded.
2. Use more drives; Moppy2 has an option for this where it uses a round-robin algorithm to assign notes to drives irrespective of channel. As each event is processed, the next drive is used. I.e. the first note goes to drive 1, the second to drive 2, etc. This ensures most of the notes at least get played, but it can sound a bit weird as different drives grab different parts of the song.
Another option is to use a "stack" of drives for a single channel, so that if there's only one note playing, the first drive in the stack is the only one that plays. If two or more notes are on simultaneously, additional drives in the stack are activated so that they all get played. You'd probably need a stack of drives for each channel (unless you just ignore channels entirely, which is a valid option), but this would let you play well beyond 16 simultaneous notes if you had enough drives and I/O pins.
2.5 Use tracks instead of channels. My understanding is that "channels" in MIDI are intended to designate a voice, rather than separate out strings of notes. "Floppy Drive" would conceivably be a single channel, and you'd use a different channel for your scanner, a different channel for the tesla coil, etc. If you're not just using round robin or stack assignments though, you'll need some way to assign certain notes to certain drives, and I coopted channels for this purpose because the Java MIDI libraries don't provide any track information with MIDI events.
If your tools allow you to see which track an event is on, you could have an unlimited number of tracks, and assign a drive to each track rather than each channel. This doesn't fully solve your chord issue (since chords will likely be on the same track in the file as well), hence "2.5", but I figured I'd throw it out there as an alternative drive-assigning method in case that's helpful (maybe there's a tool that will break up tracks into multiple tracks where each track has one note, for example).
Good luck! Do you have a GitHub repo I can star to follow your progress? Would love to see what you come up with not limited by Java's libraries.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/SammyIAm/Moppy2/issues/66#issuecomment-613697262, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACICJ6YGQGZK3M4P6BBI26LRMTKQHANCNFSM4HNDLDZA .
-- Sam Christy Robotics Instructor Medford Vocational Technical High School 489 Winthrop St. Medford MA 02155 781-393-2457 https://mvthsengineering.design.blog/
Well first, congrats on your new shop space! 😄
I also converted each of 127 MIDI notes to a relative period in preprocessing to avoid taxing the Arduino with any heavy math.
Yeah, I discovered very quickly that the additional math started to cause issues. I even went so far as to manually unroll the loop iterating over each of the drives (which I see you've done as well). Although I've learned a little bit more about keeping things efficient since I wrote the original sketch and in my most recent experiments I've written it as a for loop with some pragma to unroll it during compilation.
For all my videos, I definitely manually arrange the MIDI files to avoid overlapping notes, but my compulsion for logical-completeness meant I had to decide on some strategy (even if it doesn't get used) 😉. Admittedly I do sometimes go overboard, but I do believe that you get more reliable software if you don't rely on any assumptions about your inputs. You can make assumptions, and optimize for them, but it's usually at least worth the exercise of thinking about what would happen if two simultaneous notes snuck into a file. (As long as the results are acceptable (e.g. not "everything catches fire" or something), there's no need to do anything about it per se)
I have to say, the simplicity of being able to just stream MIDI files over serial is an attractive alternative. It misses out on some of the pre-processing that can be done in Java, but the upside is you don't really have to write any code outside of the Arduino to get it working (which I imagine is a big plus in an educational environment to keep things focused). And looking at your code has reminded me that I know about using macros in #define
statements now which makes pre-computing all the note periods way cleaner than mine (I'll have to clean that up next time I'm in the code). A very, very minor note (which won't come up unless you're working with anyone particularly musical): MIDI note 0
is C-2
, as in: C in the octave negative two, and C0
is actually note number 24
. This can be confusing sometimes if the editing software you're using tells you a note is D#2
, but your code is playing DS4
.
I'm jealous of your students because I wish I'd had the opportunity to do this sort of thing in school; it's so much more fun when you can apply things you learn to real-world, tangible results. Glad it sounds like you've got things underway, and definitely let me know if there's anything I can do to help out.
Thanks again for the helpful and detailed response. When using floppy drives, do you just keep track of the step count and reverse each drive when it comes near its limit? While the stepper motors are easy to use, they seem like a poor use of expensive motors that could be better used in a project more related to their intended purpose. In addition to using floppy drives, I am interested in using other old electronic devices creating a sort of orchestra. Do you have any experience driving dot matrix printers to make music? I also have old buzzers, door locks and other electronic devices I have collected from school.
I will let you know how it goes....whenever we get back to school. That said, I already have parents who have allowed some of my students to work with me on a limited basis over the summer. I am anxious to get our new lab up and running.
Sam
On Sat, Jun 6, 2020 at 8:02 PM Sammy1Am notifications@github.com wrote:
Well first, congrats on your new shop space! 😄
I also converted each of 127 MIDI notes to a relative period in preprocessing to avoid taxing the Arduino with any heavy math.
Yeah, I discovered very quickly that the additional math started to cause issues. I even went so far as to manually unroll the loop iterating over each of the drives (which I see you've done as well). Although I've learned a little bit more about keeping things efficient since I wrote the original sketch and in my most recent experiments https://github.com/Sammy1Am/Moppy2/tree/experimental I've written it as a for loop with some pragma to unroll it https://github.com/Sammy1Am/Moppy2/blob/experimental/Microcontroller/Moppy2-Arduino/src/MoppyInstruments/ShiftedFloppyDrives.cpp#L142-L143 during compilation.
For all my videos, I definitely manually arrange the MIDI files to avoid overlapping notes, but my compulsion for logical-completeness meant I had to decide on some strategy (even if it doesn't get used) 😉. Admittedly I do sometimes go overboard, but I do believe that you get more reliable software if you don't rely on any assumptions about your inputs. You can make assumptions, and optimize for them, but it's usually at least worth the exercise of thinking about what would happen if two simultaneous notes snuck into a file. (As long as the results are acceptable (e.g. not "everything catches fire" or something), there's no need to do anything about it per se)
I have to say, the simplicity of being able to just stream MIDI files over serial is an attractive alternative. It misses out on some of the pre-processing that can be done in Java, but the upside is you don't really have to write any code outside of the Arduino to get it working (which I imagine is a big plus in an educational environment to keep things focused). And looking at your code has reminded me that I know about using macros in #define statements now which makes pre-computing all the note periods way cleaner than mine (I'll have to clean that up next time I'm in the code). A very, very minor note (which won't come up unless you're working with anyone particularly musical): MIDI note 0 is C-2, as in: C in the octave negative two, and C0 is actually note number 24. This can be confusing sometimes if the editing software you're using tells you a note is D#2, but your code is playing D#4.
I'm jealous of your students because I wish I'd had the opportunity to do this sort of thing in school; it's so much more fun when you can apply things you learn to real-world, tangible results. Glad it sounds like you've got things underway, and definitely let me know if there's anything I can do to help out.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Sammy1Am/Moppy2/issues/66#issuecomment-640134137, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACICJ64J4XENK4MS7ZI7WLLRVLKIDANCNFSM4HNDLDZA .
-- Sam Christy Robotics Instructor Medford Vocational Technical High School 489 Winthrop St. Medford MA 02155 781-393-2457 https://mvthsengineering.design.blog/
Yes, I'm keeping track of the current position using MAX_POSITION
and currentPosition
here. Especially at higher frequencies the drives sometimes will skip a step and get out of sync, but they eventually resync either by getting to the start (where most drives will refuse to step) or the end (where the drive will make a horrible grinding noise, but it looks like there's a mechanical piece on there to handle it).
I don't have any experience with dot matrix printers, but the concept is fairly similar. The output from the Arduino is just a square wave at the frequency of the note, so it should be fairly easy to just connect that to whatever driver circuitry is needed to control the printer, door lock, etc. If you want to use them as an on-off device (like a bell, or a drum), you'll just want the signal enabled from the note-on event and signal disabled either after a fixed amount of time, or on the note-off event. The ShiftRegister class has an example of the processing required for that (though obviously with the addition of a shift register).
Floppy drives were a really easy way to start because they already include the circuitry to drive their own stepper motors.
Definitely share how things go once you get back into it!
If you're having trouble, try to describe what you've done so far and what does work so we don't ask you to try things you've already done. If you can provide screenshots, logs, or videos, they can really help get a faster response!