301

(135 replies, posted in Sinclair)

Thanks mate! Been missing you around here lately, glad you're alive and kicking smile

302

(6 replies, posted in Sinclair)

Live @ Chipwrecked 2019:

https://www.youtube.com/watch?v=CtNVPV0DNeQ

Enjoy smile

Video recording by N3M3515, I wholeheartedly recommend checking out his stuff if you're into heavy chip metal.

Sorry, got a bit side-tracked with that other new idea.
Anway, thanks a lot, of course it's great to have this here as a reference (plus it confirms I'm actually implementing things as intended).

Regarding EVN, so far I only tried the CARN flavoured variant, and was a bit underwhelmed by the results. I mean your sound examples clearly demonstrate its usefulness, but it seems rather tricky to get the implementation right. It seems that for the sound I'm most interested in (I don't know how to describe it properly, basically a sound that is still more tone than noise... how do you even measure these things? Is there a term for the "noisyness" of a signal?), useful Δs fall into a very narrow range, which is hard to hit with the low sample rates and limited 16-bit frequency resolution that I'm using. From your example it sounds like COVN may be a bit more forgiving in that respect, so I'll have to experiment more with that. I also really want to properly finish the CARN based 1-bit engine though. Well, too many projects, and too little time, as usual.

EDIT: New beeper engine using CARN is ready: http://randomflux.info/1bit/viewtopic.php?pid=2033

304

(135 replies, posted in Sinclair)

Ha, nobody expects the Spanish Inqui... I mean this new trick I discovered!

So basically I found a way to approximate a sine wave with just two square waves. Which means it's cheaper to implement than what Pytha uses to generate the triangle wave. Quality isn't as good as Pytha (mainly due to the fact that it seems almost impossible to properly align outputs to 8t multiples), but hey, how about squeezing in a 3rd channel? There's also some sweet overtone tricks you can do with this, as can be heard at the end of this short demo track. The third channel is a regular pulse channel with duty cycle control/sweep. Was hoping I could do 3 sine channels but I ran out of cycles. Either I'd need an extra register pair, or I'd need to split frequency counter updates across two loop iterations, which would probably degrade sound quality quite a bit. Overall the technique seems to be less flexible than the Pytha method as well, but I haven't explored it all that much yet.

Found an optimization for the CARN code which speeds it up quite a bit. Also, the tone channels now have envelopes.
The core loop is currently 185 cycles (18918 Hz), which means I can probably get away with cramming in another tone channel. I'm focussing on CARN at the moment because the optimization will not work for COVN.

Also that low shelf characteristic of COVN got me thinking. What if we were to also apply a lower limit to the random number range? Then the result should become tonal at some point, right?

Edit: uh-oh, the gears in my good ol' brains are rattling pretty hard right now. For one, if the spectrum of COVN can be limited in this way, then I could use that to basically simulate a band-pass filter in 1-bit. That would obviously open up a whole new range of sound possibilities. It's going to be quite tricky though because this requires a way of generating random numbers within an arbitrary range. Secondly I think I just understood why velvet noise is useful for reverb. Hmmm 1-bit reverb... hehehe...

So, here's a test modified to use CARN, for comparison. The test implementation will occasionally produce errors (manifesting themselves as short blips), but other than that it sounds quite alright to my ears as well wink

The great thing is that since there's one variable less to track, CARN can be implemented in less cycles, which is always a plus. But both COVN and CARN obviously have their uses considering the differences in sound. So ideally I'd want to come up with an implementation that allows switching between the two at runtime. I'm sure it just needs a nice piece of self-modifying code big_smile Only problem, as mentioned, I'm out of practise as far as assembly coding is concerned, but I definately want to get back to this and produce a proper engine.

In the first test example, there may be some additional problems in addition to the bad RNG. Also, the pulse density is actually fairly high in both examples. The envelope in the COVN one starts with a window size of 9 and goes up to 129. For CARN, the envelope starts with numbers < 8 and goes up to numbers < 256. In any case, the random number sequence is actually not random at all, since I'm using a fixed seed for the PRNG. If you are interested in studying the characteristics, the sequence is calculated as follows:

// for COVN
// initialization
uint_16 seed = 0x2157
uint_16 state = 0
uint_8 window_size = x  // x ∈ {0x9, 0x11, 0x21, 0x41, 0x81}

// the actual generator
// as wasteful as it looks, it's actually just 3 bytes/ 19 cycles in Z80 asm - add hl,de; rlc h
uint_16 next = (state + 0x2157)
uint_16 temp = (((next >> 8) & 0xff) << 1)
state = (next & 0xff) + (temp << 8) + (temp & 0x100)

// by coincidence, this prevents two consecutive pulses colliding at the end of the current/beginning of next window
uint_8 next_pulse_delay = ((state >> 8) & (window_size - 2)) + 1

For CARN it's essentially the same, except that the window_size is replaced with a "range" value x where x ∈ {0x7, 0xf, 0x1f, 0x3f, 0x7f, 0xff}, and the pulse delay is calculated as

uint_8 next_pulse_delay = ((state >> 8) & range) + 1

The COVN example runs at 17412 Hz and the CARN example runs at 19662 Hz (actually slightly lower, because there is a short delay every 256 samples for updating the length counter, and there can also be some minor fluctuation depending on internal machine state). The window size/range is updated every 256*32 samples. There is an additional quirk: pulses across all channels will never coincide. If more than one pulse is triggered on the given sample, the additional pulse(s) will be delayed until the next sample(s). The tone channels always trigger a "double" pulse. I found this to yield a resonable volume balance. In an actual engine we'd want to make the pulse length variable for the tone channels to fake volume envelopes for those.

There are a number of different seeds that will work reasonably well, I'm just sticking to this one because I'm lazy and and it's the only one I can remember off the top of my head. Might also be worth lifting some ideas from Shiru's noise generator code to get better random numbers.

In any case, thanks for the detailed explanation regarding the spectral characteristics. I have to admit that the point in your paper where you start to talk about Power Spectral Density was about my brain switched off, especially with those scary-looking equations on page 4 yikes

Alright, a new test, this time noise is combined with two pulse-frequency modulated tone channels. Terrible code, seems my asm skills have gotten awfully rusty. Well, it works, at least.

A question, it sounds like there is a drop in pitch with increasing window size. Is this expected, or is it an effect of the bad PRNG?

Thanks a lot for the detailed explanation! It's all very cle... wait, so there's no backtracking involved, eg. the pulse location in window n+1 is independent of the pulse location in window n? If so, what's the difference between COVN and CARN, then?

Anyway, I'm just playing around with some naïve test code at the moment, assuming the above is true. It does sound noisy alright, even using my not-so-uniform el-cheapo XORshift. Computation takes 49 cycles, which is reasonable (though I'm cutting some corners at the moment). We normally can spend up to 224 cycles on the synthesis loop (a bit more when using Pulse Frequency Modulation), which will give use a sampe rate of 15625 Hz with the Spectrum's 3.5 MHz clock.

The attached example runs at 48.6 KHz, using a window size of 16 (so a density of around 3000 if I'm not mistaken). In any case it's just a quick&dirty test, more experiments to follow later. (It's a Spectrum tape file, so you'll need an emulator - I recommend Fuse if you aren't sure which one to pick).

kurt.james.werner wrote:

I'm realizing now that perhaps old-school 1-bit trackers don't even have a fixed sample rate the way that modern audio processing (e.g. in a VST) does, but that in your context you deal just with raw clock cycles of the computer (up in the MHz?). Is that correct?

Correct, we normally deal with raw clock cycles, though it's trivial to approximate the sample rate. Assuming the synthesis loop length is fixed (which is true to some extend for most existing 1-bit engines on Spectrum), and ignoring some hardware quirks, it's a matter of simply dividing the CPU clock speed by the number of cycles in the synthesis loop.

kurt.james.werner wrote:

And if so, does that mean that on the ZX Spectrum you don't think in terms of audio samples in discrete time (e.g. a pulse wave like 000100010001) but rather just the time to flip up to +1 and a time to flip back down to 0, which need to be very close for a pulse train?

I can only speak for myself here, but I tend to think about it terms of samples in discrete time more often that not. It depends a bit on the platform - for example for PC speaker the latter approach might be more natural, due to how the hardware works. On Spectrum the common approach is to run a fixed-length synthesis loop, so it makes more sense to think about it in discrete time terms.

kurt.james.werner wrote:

I probably have a lot to learn about the terminology and basic idea of your approaches, so thanks for bearing with me here.

Don't worry too much about our terminology, it's not very formalized and everybody kind of uses their own terms. You'll probably know better how to actually name things than us most of the time. In any case, if you have any questions please feel free to ask, of course!

In any case, I have to thank you again for the great input you have been providing. I haven't been playing so much with 1-bit synthesis lately, mainly because of working on a new, experimental tracker, but also because of a lack of inspiration. Well, this discussion certainly brought back some of the latter wink If you have any more "tricks" to teach, please don't hold back! Generally any sort of signal processing with low computational requirements is of interest (never mind the "1-bit-ness" of things, it's possible to simulate multiple volume levels on 1-bit).

I think I've mostly figured it out, the only missing puzzle piece is the discrete pulse index m resp. impulse location and how it relates to the sample index n.

Regarding a real-time implementation, the main challenge will be to generate the two random number sequences. The Spectrum hardware does not provide a source of random numbers, so computing such sequences is very costly (even more so considering the computation needs to happen in constant time). The question is how much we can deviate from a uniform distribution. But that will be something to explore via experimentation, I guess.

Anyway, please take your time with the response, I won't run away in the meantime wink

Been thinking a bit more about a feasible implementation. Since we have very tight memory constraints on our machines, ideally we'd want to generate the pulse sequences in realtime. However, I'm afraid the computation might be too expensive despite it not requiring multiplication. But since the pulse sequences are sparse, it should be possible to devise a format in which they can be stored efficiently after precalculating them - essentially we could just store the distance between the pulses. However, this would take away quite a bit of flexibility. Hmmm... my main struggle at this point though is still understanding how to generate the actual kCOVN/kCARN/kCTRN sequences.

This is very, very exiting. As you correctly noted, good quality noise is pretty much uncharted territory in the realm of practical 1-bit music, even more so the possibility to control the spectrum and volume. We do have some primitive means of controlling the volume (by means of tweaking a threshold value applied to a simplified XORshift algo), but judging from the demo tune your methods obviously produce much cleaner and versatile results.

I have to admit that due to my limited knowledge of both dsp and mathematics, I can only grasp the very rudimentary basics of your paper. However, I'm very interested in implementing this on ZX Spectrum. So, I would be very grateful if you would be willing to help me understand how these techniques work. Could you provide a dumbed down explanation, or ideally some C/Python/Nyquist/pseudo-code? Also, more specifically, what do the terms "discrete-time sample index", "discrete pulse index", and "window width" mean in this context?

By the way, much obliged for mentioning us in the paper. One small note regarding this: I think Shiru deserves some credit as well, as he broke a lot of ground for the "modern" 1-bit scene as it exists today.

Anyway, thanks a lot for bringing this to the attention of our humble little internet hangout.

Neat! I guess your next music album will be an "enhanced 1-bit" one?

Great news, thanks for all your hard work. Looking forward to any hardware video captures smile

314

(21 replies, posted in Sinclair)

Thanks Shiru, that's a neat idea for spreading 1-bit sounds outside the 1-bit world.

Meanwhile I know what's up with that carrier hiss. Basically the issue seems to be caused by things leaking into bit 3 of port #fe. Which is by design in this engine, so can't be trivially fixed. Will try to rewrite the engine some day but have no time for it at the moment.

315

(4 replies, posted in General Discussion)

Another update with good news! I reimplemented the compiler generator. It's still a bit rough around the edges, but it's already much more robust than the previous version, and does not require any hard-coded work-arounds anymore. The latest incarnation of the MDCONF Standard replaces the XML-based format with an s-expression based one, which is much more compact. Needless to say it also allows me to drop all XML related dependencies (though ssax/sxml is a breeze to work with, generally). Also, MDAL now has a built-in multi-target assembler (though it only supports z80 atm), which means there's no need to provide pre-compiled binaries anymore.

Next step is to work some more on Bintracker again, so I get a better idea what kind of things MDAL needs to provide on the tracker API front.

Regarding feedback, welcome to human nature, I guess. I think it's inevitable. Still sucks, of course.

I've briefly looked into VST in the past, and indeed it seemed like a huge mess. Sadly Steinberg's stance and actions don't help to advance open source development in that field either.

In regards to DAW plugins in general, the situation is hardly better on Linux & Co. This is from the LV2 documentation:

What not to do: If you are new to LV2, do not look at the specifications and API references first! These references provide detail, but not necessarily a clear high-level view.

And then they wonder why adoption of this standard is so slow (even though the standard itself if quite good imo). And the API reference actually isn't bad, but it indeed provides no overview whatsoever.

Hehe, don't worry, it was just a stupid joke. I've been using Linux for 15+ years, but I certainly don't think it's the greatest thing in the universe. Imo it has become less attractive in recent years, too, due to certain developments. I do believe though that MS will eventually ditch their own kernal in favour of a Linux based one. 3 things that make me think that:

- MS is now one of the biggest sponsors of the Linux Foundation
- WSL2
- dropping Chakra for Chrome

While the latter has no direct connection to Linux, but it shows that the company philosophy is shifting away from doing everything in-house.

Anyway, let me just say I still very much appreciate that you took the time to make 1tracker build smoothly on Linux, even more so considering the story you told.

Very cool. Considering the popularity of bytebeat & co has only been growing over the years, I imagine this could become quite popular.

Also yup, I guess 32-bit is on its way out. Not a big fan of this development, but I don't have 32-bit compatibility on my main machine anymore either, as it mainly just increases the number of packages that need to be updated. Then again you should just come over to the dark side and develop for *nix wink Guess in a few years Microsoft will to switch to a Linux kernel for Windows anyway.

319

(4 replies, posted in General Discussion)

Unexpectedly got some more free time before that other project I mentioned. So here's a little sneak preview:

https://i.ibb.co/CPVzhg9/btng001.png

Hi Bushy, welcome aboard!

Glad to see someone being so enthusiastic about 1-bit sound wink And great to have you doing all these engine ports. My first successful attempt at 1-bit code was a Huby port as well wink I'm sure after a while you'll figure out how to write your own engines as well. If you have any questions, feel free to ask, of course.

I'll see if I can dig up some .1tm files from my archive and send them your way. Unfortunately Gmail has a nasty habit of blocking attachments of unknown file types, so if you don't receive a mail from me please let me know here.

321

(4 replies, posted in General Discussion)

More progress. Got rid of most of the hard-coded compiler parts, so the compiler generator is getting there slowly. (Yes, you read correctly: the new libmdal generates a custom compiler for each target engine, rather than using one big standard compiler with loads of conditionals like in libmdal v1).

Also, I was able to implement a new, much more robust parser using comparse, thanks to its friendly author who gave me an in-depth tutorial last weekend.

Last but not least I did some much needed refactoring, finally breaking up that horrible 3000 lines-of-code core module into various submodules.

Unfortunately in the coming weeks I'll have to work on another, unrelated project, so development will be stalled again until at least the end of the month.

322

(4 replies, posted in General Discussion)

Great news! The new libmdal compiler produced it's first correct output today. Still cheating a bit by hard-coding a few parameters, but nevertheless it's a major step forward. Testing with Huby at the moment which may not sound all that exiting. However, Huby has some not-so-common quirks so it's good for testing a number of different features (fixed pattern size so MDAL source patterns must be resized, size of order list must be stored in data, which requires multiple compiler passes, and so on).

Still a lot of work to do, but for now I'm very happy smile

323

(20 replies, posted in Sinclair)

I don't think there is a way to fix it. Even changing the pitch will not make the samples play as intended.
Interrupts and beeper sound don't mix well. And iirc Nirvana's interrupts take up almost the entire frame.

324

(20 replies, posted in Sinclair)

Hi,

Since Shiru seems to be busy, I'll try to answer in his place.

I don't think there is any code for the method Shiru describes, because nobody has implemented it. The idea for continuous sound would be not to call the sound generator through an ISR (because 50 Hz is too slow to make anything but the lowest audible tones), but to interleave it with the multicolour code at regular intervals. The usual restrictions with contention apply there, so it would still be quite tricky. Generally, PFM/pin pulse and Zilogat0r's Squeeker method would work best because they both can work with a low sample rate.

For sfx and simple melodies consisting of short blips, doing this on interrupts works fine. Avoid the Basic Beep, as it has a lot of overhead. You can achieve the same result with just three commands:

  add hl,de
  ld a,h
  out (#fe),a

Init DE with the desired frequency divider (for tests, something like #0040 will be fine), and run in a loop for something like 1000 times.

In z88dk, there's also a modified version of the Tritone engine with can be used in combination with game logic. It basically runs all the time and then periodically runs your game code on note changes. If your game logic is simple and doesn't need a lot of CPU time, it might be a good option.

325

(3 replies, posted in Sinclair)

Hahaha this made my day:

;да

I always fail to find an elegant way of commenting a conditional jump. I think this is the perfect way.

Other than that, I can only reiterate what Shiru said: This code is meant to be called through an IM2 interrupt, or some other form of loop.
This appears to be the relevant z88dk documentation: https://github.com/z88dk/z88dk/wiki/interrupts