-
Notifications
You must be signed in to change notification settings - Fork 179
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
Music sequencer #15
Comments
Is it possible to make some kind of binary custom MML? Like, for example, if pairs of hex digits represent individual MML instructions, that might let someone who wants to type everything into the source code directly do so, but keep data relatively compact for people exporting from external tools. Maybe |
I love that idea! We could provide macros/inline functions to make it easier to type into source code. static unsigned char music[] = {
VOLUME(50) | SUSTAIN(30) | DECAY(10) | BPM(160),
C, D, E, F, G,
}; |
For sure, yeah! That said, I think a single byte isn't going to be able to record all the control modes? PICO-8 makes me think eight volume levels is near the minimum I'd want (3 bits), there are four WASM-4 channels (2 bits), the first two each have 4 tone modes (2 bits), BPMs ranging from under 40 (90 frames per quarter note) to over 200 (18 frames per quarter note) are all common (say, 5+ bits if we use intervals of 4 frames), note lengths from sixteenth notes to whole notes with dotted versions are all common (3+ bits) ... and we're already at 17 bits worth of parameters when we only have room for 7, and we haven't touched envelope parameters yet. Or dedicated control codes for rests and frequency glides. I'm not sure what the most logical way to break it up is (probably a mix of 'this byte range specifies these' and 'this control code specifies what the next byte or bytes mean'), but thinking musically:
|
I guess what I'm imagining right now is something along the lines of:
...plus room for expansion. |
Thanks for the insightful details! That bit layout looks perfect! If I understand correctly, this would be fully compatible with MML text mode? So the byte stream could contain both the single-byte control code to set the volume or the string, eg: "v75".
That sounds good to me. I researched a bit into how MML handles multi-channel but couldn't find anything somewhat standard 🤷 Are you aware of any MML editors? Being able to use existing tooling around this sort of thing would be nice. |
Sure thing!
I ... would assume not? I think any ASCII characters would be in the 0-127 range that would be interpreted as note pitches. I don't actually know enough about data structures to understand how this works - I thought Maybe there could be a string mode? I don't know.
Not personally, but when I was poking around looking for information on historical use of MML-formatted data, I found the VGMPF wiki page on it with a big list of examples. Maybe some of those would be good inspiration? |
I think this would be closer to your original idea of using ASCII for 0-127 and reserving 128-255 for control bytes. So the string can either contain the 3-byte sequence "v50" to set half volume, or a single byte 0b10vvv000 using the above bit layout. One is nicer to write in source code, the other is better for tool output. Anywho, just an idea, and probably premature optimization 😄 It would be nice to get an implementation going, MML or otherwise, and iterate from there. Do you have any thoughts on BeepBox/FamiStudio? |
Oh, question: would it make sense to have "start loop" and/or "end loop" control codes in the sequencer? I think there should probably be some way to implement looping background music; that might be a way to do it. |
Looping sounds great to me. In any case we'll need a way to stop a running music sequence from code, probably with another function.
Agreed 😄 |
Another thought I had: how hard would it be to provide the source code for the music sequencer in a way that's straightforward for programmers to copy into their own code and modify? I feel like there'll be less pressure to add new features to the built-in sequencer functions if you make it easy for people to mod tremolo or vibrato or whatever else in themselves. (Edit: Unrelatedly, I've started looking through the documentation for FamiStudio - I haven't written anything in it yet, but it definitely feels like a good choice to make an importer from. It even defaults to the same four channels - square, square, triangle, noise - which I imagine would make conversion a lot easier because there's no ambiguity about which channel means what.) |
Yeah, WASM-4's sound system is basically the same as the NES minus the DMC channel, so it should map pretty well. I'd urge you to give BeepBox a closer look... behind the cutesy names is a powerful but accessible tool. |
I'm sorry, I have to set a boundary here: don't urge me to do things that I've told you I'm not going to do. In any case, my dealbreakers don't have to be your dealbreakers; I mostly brought the scales thing up as a reason not to standardize on BeepBox as the primary or only external tracker to import music from. I'm sure I'm not the only person who would have an issue with that - in fact, I know I wouldn't be, because I talked about it with some friends and they said it was "yipes" and "incredibly frustrating" and "patronizing as [expletive removed]" - and it's better if there are options that don't provoke those reactions. Accessibility is a good thing, though - I don't have any particular suggestions on that front. FamiStudio is still pretty intimidating and I don't have a comfortable workflow in it the way I do with PICO-8's built-in tracker. (So much clicking...) |
Another thought, looking at FamiStudio: the idea of making patterns and stringing them together into sequences is probably transferable and would save space. Probably not something for the first test version of the sequencer, but maybe to add later? |
Yeah, the concept of a bank of patterns is interesting and seems to be shared across different tools' export formats. Both FamiStudio and BeepBox's export formats look more or less like this:
That is to say, it shouldn't be hard to support multiple authoring tools. |
Regarding implementation, are you thinking that this sequencer would be implemented in the "hardware", or as a small optional library bundled into carts which would use the existing I'm asking mostly because I think the former would add unneeded complexity to the console itself. It also raises the question if For having a more precise BPM when using What are your thoughts? I find all of this very interesting. |
My current plans are leaning toward designing some kind of simple music bytecode similar to tracker formats. Multiple source languages/tools could then compile to that bytecode. This would be in the runtime itself as a convenience, and be implemented entirely using Normally I'm not keen on adding stuff to the runtime, but music is so common, and I think we can come up with a design that's minimal. |
Lots of thoughts above on the "how" just wanted to mention this is [obviously] not a new space... lots of prior art we can look to for inspiration. Just a few things I've personally worked with:
Having a |
Thanks, those are useful links! About 60hz boundaries, maybe, but I'm not thinking of this as adding any new functionality on top of the lower level |
Hello, I recently started using wasm4 and got stuck implementing music. I want to share my thoughts about this. I wish there was a memory mapped buffer where I could put instructions for the audio system ahead of time. This would bring 2 benefits:
|
This is a pretty good idea! The API command could be as easy as As I see it this leaves us with multiple options: 1. Keep all the limitations of
|
@JerwuQu thank you. I think 1ms timing granularity might not be enough for some complex compositions, maybe timing can be a number of samples in 44100 frequency? So 44100 = 1 second. Or maybe just using floats where 1.0 = 1 second. I didn't consider something when writing my initial comment: usually a memory mapped buffer interface assumes that you put commands on it each frame and in the next frame it would be cleared (similar to the framebuffer), but this would limit audio commands to only be as long as one frame, which means |
True, 1 millisecond isn't perfect, but it'd leave us with over 16x the amount of usable BPMs (without any time aliasing) compared to the current frame-based limitations. I do not believe it would be a limitation in practice. A real example would be making your 140 BPM 4/4 song instead be 140.187 BPM, or your 149 BPM song instead be 148.515 or 150.0 BPM. Here's how this was calculated in w4on for the current timing system. I suppose increasing the duration field to an |
Music can be implemented right now by games calling tone() at the right times. It involves writing a bunch of code to handle the time sequencing... but it's a lot of work.
Let's build a music sequencer and a music authoring format into the runtime. The goal is a new
music()
method which takes something as a parameter to play music using the 4 channel audio system.There are some approaches here for that something:
w4
command similar to png2src that generates the blob in source code.The music format should be usable for short sound effects too and not only background music. Maybe the function should be called something like
track()
orsequence()
instead ofmusic()
to reflect that.Right now I'm leaning towards BeepBox, and a
w4 beepbox2src
command that takes a beepbox.co URL or json export and spits out binary data in source code.The text was updated successfully, but these errors were encountered: