Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[synthio] add a simple MidiTrack implementation #4447

Merged
merged 3 commits into from
Apr 13, 2021

Conversation

tyomitch
Copy link
Collaborator

for QBasic-compatible music strings
(https://en.wikibooks.org/wiki/QBasic/Appendix#PLAY)

The music parser is WIP and comments are welcome
before the input validation and missing features
are added; in particular, would microbit syntax
(https://microbit-micropython.readthedocs.io/en/v1.0.1/tutorials/music.html#wolfgang-amadeus-microbit)
be preferable?

@tyomitch tyomitch force-pushed the patch-3 branch 7 times, most recently from 99b6c9e to 3ad6e4d Compare March 20, 2021 14:36
@tyomitch tyomitch marked this pull request as ready for review March 20, 2021 15:58
@jepler
Copy link
Member

jepler commented Mar 22, 2021

This looks neat!

I wonder whether we'll want to place it in its own separate module, like we did with audiomp3.

Good find on space savings :)

ports/unix/mpconfigport.h Outdated Show resolved Hide resolved
@tyomitch
Copy link
Collaborator Author

I wonder whether we'll want to place it in its own separate module, like we did with audiomp3.

Isn't it too simple for a module of its own? MP3Decoder (including mp3dec) has 2470 bytes of code, Music only 546

@tannewt
Copy link
Member

tannewt commented Mar 22, 2021

It should definitely be in a module of it's own because 546 can be the difference to fitting things in. Why do this in C at all? Can't Python do it? The RTTTL library is very similar but in Python: https://github.com/adafruit/Adafruit_CircuitPython_RTTTL/

Perhaps there is a lower level API we need to add. I highly doubt we need to do any parsing in C though.

@tyomitch
Copy link
Collaborator Author

Why do this in C at all? Can't Python do it? The RTTTL library is very similar but in Python: https://github.com/adafruit/Adafruit_CircuitPython_RTTTL/

The main difference is that adafruit_rtttl cannot play things in background while other Python code runs.

Perhaps there is a lower level API we need to add. I highly doubt we need to do any parsing in C though.

It would be nice to be able to implement AudioSample classes in Python, but I thought there was a conscious decision to disallow any form of concurrency in Python code? And get_buffer() has to run concurrently with other Python code.

@tannewt
Copy link
Member

tannewt commented Mar 23, 2021

Why do this in C at all? Can't Python do it? The RTTTL library is very similar but in Python: https://github.com/adafruit/Adafruit_CircuitPython_RTTTL/

The main difference is that adafruit_rtttl cannot play things in background while other Python code runs.

Yup! True.

Perhaps there is a lower level API we need to add. I highly doubt we need to do any parsing in C though.

It would be nice to be able to implement AudioSample classes in Python, but I thought there was a conscious decision to disallow any form of concurrency in Python code? And get_buffer() has to run concurrently with other Python code.

Ya, we don't really want to add concurrency because of all of it's pitfalls.

I wonder if a better way to do this would be to have a synthio module with a class that takes in a midi stream and generates audio samples. Or it could just generate control signals like eurorack. MIDI would be a good way to buffer changes into the sound generation code.

@tannewt
Copy link
Member

tannewt commented Mar 23, 2021

Filed this issue: #4467

@tyomitch
Copy link
Collaborator Author

I wonder if a better way to do this would be to have a synthio module with a class that takes in a midi stream and generates audio samples.
MIDI would be a good way to buffer changes into the sound generation code.

There you go :) Essentially the same code, but taking MIDI stream as input.

@tyomitch tyomitch force-pushed the patch-3 branch 3 times, most recently from 51cd048 to 59875f8 Compare March 26, 2021 18:56
Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! That's awesome. One question about the API. I think we'll wait to wait until after 6.2.0 to merge this. That will give us time to refine the API if we need to.

//| """Simple square-wave MIDI synth"""
//|
//| def __init__(self, buffer: ReadableBuffer, tempo: int, *, sample_rate: int = 11025) -> None:
//| """Create a MidiTrack from the given stream of MIDI events. Only "Note On" and "Note Off" events
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about not taking a buffer and having a write method instead that queues data up? That way you can pipe midi from BLE or USB directly into this object.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point, but:

  • that would make looping (or even passing the same AudioSample to play() repeatedly) impossible;
  • the protocol for audiosample_reset_buffer_fun has no way to report failure if the AudioSample is not rewindable;
  • if the queued MIDI stream depletes during playback, the playback will stop, instead of waiting for new input. For real-time input, when the MIDI events arrive at the same speed that they're played, this would happen after every event!

Perhaps a new protocol, e.g. AudioStream, should be defined alongside AudioSample, and Audio*Out implementations will need to be updated to handle the new kind of input -- e.g. a new method play_stream which wouldn't take a loop parameter. Then synthio can have MidiStream alongside MidiTrack, with much of the implementation shared. Similarly, there can then be RawStream alongside RawSample.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are all really good points. I'd like to get @jepler's thoughts on it too since he has thought about MP3 streaming some.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tyomitch This all looks good. Would you mind putting your comment above in an issue so we can think about that for the future? I'll merge this now.

@tyomitch
Copy link
Collaborator Author

tyomitch commented Apr 6, 2021

I think we'll wait to wait until after 6.2.0 to merge this. That will give us time to refine the API if we need to.

@tannewt @jepler ping?

@jepler
Copy link
Member

jepler commented Apr 7, 2021

I haven't had time to look into that with any detail. (or test it)

I think the main thing is putting it in the right place, module-wise. How to handle repeat=True I think we need to look at, but the time developing 7.0 may give us an opportunity -- and may end up changing how this works in the process.

@tyomitch tyomitch changed the title [audiocore] add a new AudioSample implementation [synthio] add a simple MidiTrack implementation Apr 8, 2021
@tyomitch
Copy link
Collaborator Author

tyomitch commented Apr 8, 2021

I think the main thing is putting it in the right place, module-wise.

Sorry for forgetting to update the PR title; it no longer targets audiocore

tannewt
tannewt previously approved these changes Apr 8, 2021
Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@tannewt
Copy link
Member

tannewt commented Apr 8, 2021

Looks like it needs a translation merge. Sorry about that!

tannewt
tannewt previously approved these changes Apr 8, 2021
Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update!

@tannewt
Copy link
Member

tannewt commented Apr 9, 2021

Looks like three boards are out of space. The simplest fix is to disable the module for those builds.

region `FLASH_FIRMWARE' overflowed by 228 bytes
@tyomitch
Copy link
Collaborator Author

Looks like three boards are out of space. The simplest fix is to disable the module for those builds.

Done! #4583 took care of two boards; the third one had to have synthio disabled even so.

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Thank you for this!

@tannewt tannewt merged commit b1f4a9a into adafruit:main Apr 13, 2021
@tyomitch tyomitch deleted the patch-3 branch April 28, 2021 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants