I've had 90% of this lying around for a few months and decided to finally finish it.

Its main features are:
  • 4 square wave channels
  • 16-bit frequency control
  • 8-bit duty cycle control
  • L/R mask control
  • jumps inside the song data
  • support for arbitrary code execution to facilitate complex jump structures within the song
  • ~17 kHz effective sample rate (depends on CPU frequency)
  • ~68 Hz refresh rate (depends on CPU frequency)
  • 0cc delay when reading the next note
  • reading song data directly from the archive
  • the entire player is 496 bytes (EDIT: 512 bytes after debugging and a few nice features like pressing ON to interrupt playback)

Its cons are:
  • doesn't run on regular 83+
  • no noise channel
  • file format is pretty big at 6 bytes per note
  • channels are not separated inside the song data meaning you can't have one channel repeating a short bit and another channel doing a longer bit, although the arbitrary code execution it supports could help by generating song data on the fly
  • there is no GUI, right now you just store the name of the appvar you want to play into Ans


Here are a few previews of the sound:
1bit.wav (77 bytes of data) (recorded at 192 kHz samplerate)
lifecaps.mp3 (173 bytes of data) (resampled to 22050 Hz to get rid of aliasing artifacts that happened because I was recording it at 44100 Hz)

The binary data for the second one of these was generated by a script I made that takes care of the quirks of the format for you. This would make it possible to create longer songs (as long as they don't surpass the 32 KiB maximum filesize this thing has).

I plan to release this in a few weeks, probably. However, I also want to create a challenge for you all in reverse-engineering it, so I will probably not release the source code yet.

Here is the binary data of lifecaps.mp3, try to figure out the format! (shouldn't be too hard because I aligned it already)

Code:
00000000: 4602 80 03 0012      F.....
00000006: 0803 80 03 0024      .....$
0000000c: 8401 4c 03 0036      ..L..6
00000012: c200 4c 03 0036      ..L..6
00000018: d203 80 03 0048      .....H
0000001e: 8b04 80 03 0012      ......
00000024: dd02 80 03 0012      ......
0000002a: e901 4c 03 0036      ..L..6
00000030: f400 4c 03 0036      ..L..6
00000036: 8b04 80 03 0024      .....$
0000003c: b905 80 03 0012      ......
00000042: 1905 80 03 0012      ......
00000048: 4a04 80 03 0024      J....$
0000004e: 2502 4c 03 0036      %.L..6
00000054: 1201 4c 03 0036      ..L..6
0000005a: 6703 80 03 0024      g....$
00000060: 8d02 80 03 0012      ......
00000066: 2502 80 03 0012      %.....
0000006c: dd02 80 03 0024      .....$
00000072: 8d02 4c 03 0036      ..L..6
00000078: 4601 4c 03 0036      F.L..6
0000007e: 6703 80 03 0024      g....$
00000084: 4a04 80 03 0012      J.....
0000008a: dd02 80 03 006c      .....l
00000090: d203 80 03 006c      .....l
00000096: 6802 4c 03 006c      h.L..l
0000009c: 3401 4c 03 006c      4.L..l
000000a2: 0000 00 00 4100 0000 ....A...
000000aa: c397dd               ...


EDIT: For posterity, I already explained the format on the Discord channel #z80-ez80.

EDIT: I also already shared the program binary and source in chat. However, although the source is mostly correct, it is a slightly newer version (that is also broken by the way), because I overwrote it. It's mostly the same though, and combining a disassembly of the binary and the source I did include should be good enough.

EDIT: I recovered the original source and added more comments!
This is the song I'm currently working on: mimiga_village.mp3. (by the way both this and lifecaps.mp3 above are covers from the OST of the game Cave Story) The program size has also grown from 496 to 506 bytes (with no new features but fewer bugs), while the size for each channel's player has grown from 47 to 58 bytes, with a faster native jump and better (primarily less buggy) support for arbitrary code execution.

Around 35 seconds is a demonstration of a different duty cycle and of stereo sound. Measures 1-5 (seconds 1-12) are reused for measures 9-13 (seconds 18-29), which was achieved using the aforementioned arbitrary code execution. The fadeout at the end was added in post (originally it keeps repeating forever).

The fourth channel is completely unused during all of this, and I hope to use it for percussion by playing a low duty cycle, high pitched note with lots of aliasing artifacts. In the beginning, the 2nd and 3rd channels are usually playing the same note on a different rhythm, which makes it sound weird. I'm looking for solutions. Also, I didn't finish the 2nd channel yet, it ends around the 50-second mark.

Finally, this song's binary data was completely generated using my script (which keeps getting more and more features and fewer and fewer bugs). Here is a screenshot of the source file format:
Whoa, super cool, I love it! Why doesn't it run on a standard TI-83+?
That is really snazzy! Out of curiosity, and based on the sound quality, is it using pretty much 100% of the CPU, or is there time for the CPU to do other things (like for a game). If the sound is being timed by interrupts, I imagine that might leave time open for some graphics that allows gameplay at the same time.
CalebHansberry wrote:
Whoa, super cool, I love it! Why doesn't it run on a standard TI-83+?

It uses the 15 MHz mode (you can tell my script the exact frequency so it can generate the data with better tuning) and also a memory mapping only available on the 83+SE and above (port 05 and 27, though it would be possible to work around this).

Xeda112358 wrote:
That is really snazzy! Out of curiosity, and based on the sound quality, is it using pretty much 100% of the CPU, or is there time for the CPU to do other things (like for a game). If the sound is being timed by interrupts, I imagine that might leave time open for some graphics that allows gameplay at the same time.

It's a little complicated. I am using SP as a pointer into the song data (POP is by far the fastest way to read a word and increment the pointer). Except for that, the playing of the notes by itself only takes a little over half the CPU time (136 out of 216 clock cycles right now, per channel, although interrupt overhead (EXX, EI, resetting the timer...) would add quite a lot to that), but I idle on every loop to make sure the delay is always the same as when reading a new note. If I were to give up on this (it only reads a new note every 1/1000+ loops, after all), it could be made quite a bit faster. I might also make a lighter engine with fewer features or with duty cycle and L/R mask only available through arbitrary code execution, or with fewer channels (having 4 channels requires an actual sample rate of at least 60 kHz or so for it not to sound horrible, but the higher you get it the better it sounds, currently the actual sample rate is 69444 Hz and the effective rate is 17361 Hz, both at exactly 15 MHz, for me it was 18374 effective and 74296 actual). In any case, the reason it sounds so good is probably because of the 16-bit frequency precision. Most notes in the common range are less than one cent (that's 1/100 of a semitone) off (it only goes above 2 cents below the A#1, though this depends quite a bit on the CPU frequency you set (which was 16048000 for me), for example setting 15000000 makes that same A#1 be 0.29 cents off, while the F2 is 2.24 cents off). Also, the thing I call frequency really is the frequency, not the wavelength. This is what allows me to have a variable duty cycle, and decrementing a 16-bit counter and checking against zero would not take less time than my current approach anyway. An 8-bit counter would be much faster to decrement (because I don't need to waste another 8cc to check the Z flag), but that would make for horrible tuning.
First, let me say that this program is amazing. I actually plugged my HD6xx into my 84 and could totally hear the song. It was amazing! I'm definitely gonna try my hand at composition on this thing, as well as make an on-calc editor.

On the flip side, this seemed to brick my calculator. Even after a hard reset, the calculator seems to be running about 5% its normal speed. It's usable, but you have to hold down a button for several seconds every time you want to do something. Do you have any idea what's going on?
Sam wrote:
On the flip side, this seemed to brick my calculator. Even after a hard reset, the calculator seems to be running about 5% its normal speed. It's usable, but you have to hold down a button for several seconds every time you want to do something. Do you have any idea what's going on?


You need to remove the headphones before you exit the program (it even tells you about this). This resets the link port properly so that the OS doesn't get confused. If you didn't insert/remove the headphones when the program told you to, remove the headphones and run the hex code AFD300C9 (xor a \ out (0),a \ ret), which also resets the link port.
Off an on over the past few weeks I've been working on a Python script to convert a MIDI file into a readable format that works with fghsgh's plaintext to .bin converter. It's a really roundabout way to do it, but just as fghsgh was with the plaintext to .bin converter, I was optimizing for easy. Forget that you have to do .mid > .fghsbeep > .bin > .8xv, lol. Anyway, here's the script: https://pastebin.com/vDADiTvz
You'll need to install this Python MIDI library. Also, the program doesn't really work too well right now because I haven't taught it to differentiate between a note off and not changing a previous note off. If anyone wants to continue where I left off, feel free. It might not create something that will compile immediately with fghsgh's converter, but at least it will get the gist. I'll probably work on the rest of it later.
Wow, just discovered this, thanks to @Xeda112358. Super stoked to see a new 1-bit engine for 83+/84+. Sounds great, and the source is clear and readable.

Would you be willing to release the source code under a MIT/BSD/Apache type of license? Then I can try to add the engine to my work-in-progress chipmusic tracker.
utz wrote:
Wow, just discovered this, thanks to @Xeda112358. Super stoked to see a new 1-bit engine for 83+/84+. Sounds great, and the source is clear and readable.

Would you be willing to release the source code under a MIT/BSD/Apache type of license? Then I can try to add the engine to my work-in-progress chipmusic tracker.

Oh wow it's you! You're actually what inspired me to write this back then. Always feels great to be noticed by someone you look up to.

Anyway, I consider this one to be more of a proof-of-concept, so I didn't really want to properly release it under a license. However, I had started working on a second version that uses a much more standard file format, based on chiptune (especially SID) engines. It stagnated, but I picked up work on it a month or so ago and it looks like it's going to get finished one of the following weeks.
Okido, I'll wait for your new engine, then. Feel free to ping me here when it's ready, I don't really keep tabs on cemetech anymore these days.

Don't worry too much about the data format. For 1-bit engines, the priority is to make data loading fast. I almost always use stack-based loading as well. I don't even bother to do a lookup for note frequencies, so I just store divider values directly. I frequently use detune as an effect, and storing plain div values gives you that for free. Another great trick (if you haven't found it yourself yet) is to pop the first word into AF, treating F as a set of flags to skip data that doesn't change between rows. Anyway I put some ramblings on 1-bit engine design here. It's pretty ZX Spectrum focussed, but you might find a useful thing or two there.
utz wrote:
Don't worry too much about the data format. For 1-bit engines, the priority is to make data loading fast. I almost always use stack-based loading as well.

Well, the thing is, the actual sound playing in the new one happens in a 176cc interrupt, and it leaves more than enough time for parsing a nicer file format. But this also means SP has to be used for its intended purpose... But with a Z80 running at 16 MHz, this is all doable. Plus, it completely eliminates row transition noise! (which the previous version also didn't have somehow, by the way) (but then again, it adds some noise from instructions not always taking the same amount of clock cycles)
utz wrote:
Another great trick (if you haven't found it yourself yet) is to pop the first word into AF, treating F as a set of flags to skip data that doesn't change between rows.

In the old one, the flags were actually used to trigger jumps. Now, again, the new one won't be using POPs to read data, because SP is taken, so this is off limits.
utz wrote:
Anyway I put some ramblings on 1-bit engine design here. It's pretty ZX Spectrum focussed, but you might find a useful thing or two there.

Yeah, saw that. That and this interview you did is what got me into this stuff in the first place! But the goal here is to take advantage of the extra 12.5 MHz to both make it sound better, and make the file format easier to write, and more compact. I don't have access to a proper soundchip but I still want to make chiptune music, basically, so I'm emulating my own. The interrupt routine essentially emulates a full-on soundchip with memory-mapped ports (aka SMC of immediate values in the routine), and that leaves about 250 kHz outside of the interrupt for processing input (if I recall correctly and if my calculations were correct), which is more than you'd have, for instance, during a vblank interrupt. Another advantage of this is that it doesn't have to be dependent on the CPU speed (so the tuning and rhythm is always accurate).
Very cool stuff - I wish I knew more about sound personally.
fghsgh wrote:

Well, the thing is, the actual sound playing in the new one happens in a 176cc interrupt, and it leaves more than enough time for parsing a nicer file format. But this also means SP has to be used for its intended purpose...


I see. Sometimes it's worth using SP even with the overhead of having to store/restore it, but in 176cc you probably don't want to do that.

fghsgh wrote:

Plus, it completely eliminates row transition noise! (which the previous version also didn't have somehow, by the way)


I've come to think that row transition noise can mostly be eliminated by preserving note states, like you do in your v1 player. Not doing that is one of the major design flaws in Houstontracker 2, but I didn't know better back then Surprised

fghsgh wrote:

(but then again, it adds some noise from instructions not always taking the same amount of clock cycles)


1-2 cycles deviation probably don't degrade the sound all that much. The synth loop in HT2 was fluctuating by 1t at some point, and I'd argue fixing that actually made the sound worse - instead of a subtle fuzz you now get a subtle whine. On Spectrum we have to be precise because missing a port write by just 1 cycle can result in several cycles of delay, but on TI, especially in 16 MHz mode, I'm pretty sure you can get away with being a bit sloppy.
How on Earth did you achieve stereo music with 1 bit?
Beakbonk wrote:
How on Earth did you achieve stereo music with 1 bit?

Uh, alright, so on port 0, bit 0 is the left channel (tip of the connector) and bit 1 is the right channel (ring of the connector) (and the sleeve is ground) (tip-ring-sleeve is what you call the contacts on a regular TRS audio jack). Toggling between 0 and 1 only plays left, toggling between 0 and 2 only plays right, and toggling between 0 and 3 plays both. And toggle the bits individually if you want to play different things in both channels. Also see the documentation for the port.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement