versatile microcontroller based soft synth design

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
versatile microcontroller based soft synth design
by on (#206918)
Well it's time for another one of my design idea ramblings. Not really expecting many to read all this, but taking the time to write all this up has proved helpful with some of my projects as of late. As one may have noticed with some of my recent postings, I've taken on the goal to replicate some common Famicom audio expansion synths with low cost cartridge hardware. This is something I've wanted to do since getting involved with nesdev, but only now am I getting to the point where I can finally realize the idea having overcame the hurdles below:

Console Support
This is one of the biggest hurdles IMO, because if the average NES player uses a front loading NES system lacking built in cartridge audio expansion. Requiring users to modify their consoles by means of soldering can be considered a deal breaker. I recently came up with and teased a solderless audio expansion dongle for front loading NES's. The prototype was successful and most parts are on hand to support manufacturing and release in the upcoming months.

Armed with this, the biggest thing lacking is support for top loader consoles. Not much can be done about that for a solderless solution, routing audio to EXP9 as Bregalad did is the only option I see. To help support that my designs output audio to both EXP6 & EXP9 or have a jumper to connect the two.

There's no hope for cheap clones, and IMO any player who values audio quality wouldn't use one of these due to common duty cycle error and other incompatibilities. I can only hope that high end clones such as AnalogueNT, and AVS were designed with audio expansion support in mind. I haven't been able to confirm they have, I assume they support it via their famicom cartridge connector however.

Cartridge Hardware Cost
To date, the common means to replicate traditional audio expansions is with high density expensive FPGAs. This is a non-option for a homebrew game seeking cartridge release. I have some crazy newfangled projects I'm working on that would bring enough logic to the table on a manageable budget; but the fact remains most homebrew targets discrete mappers for good reasons. There are drawbacks of course, but cost of entry is significantly lowered when utilizing mcu based solutions.

The amount of mcu horsepower that can be purchased for under $1 has significantly improved thanks to the low cost of ARM mcus. It can still be a challenge to push the cost down with a stand alone resistor based DACs running ~$1 itself. The added cost of a separate mcu and DAC effective make it so the only sensible choice is to choose a mcu with built in DAC. Thankfully there are several cost effective mcu's with built in resistor based DACs for close to $1 in qty. That gets things on par with the cost of an ASIC mapper such as MMC1/MMC3/FME7, but the fact remains majority of homebrew targets discrete mappers.

With desire to push the cost even further I recently opened my mind to the viability of PWM based DACs. Armed with a single resistor and cap any mcu's PWM timer can generate reasonable quality audio. I learned most of what I know of PWM DACs from open music labs, and this project is legitimate enough proof of concept to keep myself from rejecting the idea despite the ugliness of PWM noise.

I've even gone so far as to push things to near free BOM cost by integrating the synth with a multitasked lockout chip "CICOprocessor". So at this point I'm mostly at the why not stage as I see a path to permit publishing of homebrew games with audio expansion with minimal added cost. I have a couple other designs in the works with more capable hardware at their disposal as well. This discussion is not intended to be focused on any specific board, mcu, DAC, etc, that's why I've separated this discussion to a dedicated thread. I am however using the CICOprocessor/STM8 as my starting point. My thought is if this general synth design is realizable on what is arguably one lowest cost 8bit mcu on the market, adapting it to more capable hardware won't be a problem. That and I got to the point with my CICOprocessor synth that I needed a better sense of actual compute time needed for the synth to get a better sense of what's possible. Load, add, multiply, store all roughly take a comparable number of cycles regardless of the selected mcu, so decisions that help one mcu will likely help whichever mcu is chosen; the same is not the case when comparing to a programmable logic implementation.

I realize there are trade offs with this lower cost mcu approach that are unlikely to not be acceptable to audiophiles. Arguably satisfying audiophiles can't be done on a budget, they're typically only satisfied with shelling out top dollar for originals or new old stock. I wish them happiness, but in the end success of lower cost solutions will only help pave the way for higher fidelity future projects. I recognize the battle with aliasing will forever be present. Admittedly I'm also not experienced in audio synthesis, I'm just now starting to feel like I'm breaching past noob level. But I've learned quite a bit from several members here in the few discussions I've already had on the topic. I'm thirsty to learn, so don't be shy about telling me where my ideas are flawed or where there may be tricks to be take advantage of.

Community & Tool Support
Aside from the actual hardware and firmware implementation this is the final hurdle. It's safe to say I personally won't go as far as adding support for said audio expansion to emulators or trackers. Tool support is pretty vital to success of this project, so the only viable option I see is to bootstrap myself in by mimicking traditional "standard" synths which are already supported by current tools. Some idea brought up by several members in the CICOprocessor thread have led me to a mcu synth design which can be configured to mimic VRC6 audio. The focus of this thread is to cover the actual mcu implementation which can be easily adapted to a chosen mcu. The interface details such as NES register structure, R2R/PWM DAC, synth sample frequency, or other mapper details are superfluous. My hope is that this mcu synth structure can be adapted, migrated, and utilized by other designs including other people's hardware designs. Or perhaps some details such as sample frequency will be synth settings that can be modified in real time.

The underlying synth I've designed thus far replicates the VRC6, but actually utilizes wave tables to do so. I already have a rough idea of how it can be configured to support Sunsoft-5B with minimal additions barring some questions in my mind on detailed operation of the YM2149F. I've yet to fully wrap my head around the inner workings of namco-163, FDS, and MMC5, however my current basic understanding of those has me relatively confident they too can be supported with a necessary additional features.

Being an mcu based solution the overall goal is fast execution. While avoiding unnecessary computations helps, optimizing the synth design to the point where it is optimized specifically for certain synth it detracts from larger goal of being versatile/universal. At this point it's better to leave those synth specific optimizations for low hanging fruit when it comes to the implementation stage. In a way my idea here is more along the lines of creating a super set of features required to replicate traditional synths. Every single feature doesn't always have to be implemented in practice depending on design constraints. With this approach, many of the underlying features are already present in the hardware should an 'advanced user' desire to start tinkering with all the advanced settings instead of only using the vanilla "VRC6" configuration. There are a design ques I took from namco-163 that you might notice, I got a better picture of how a mcu friendly soft synth might look after gaining a better understanding of namco-163 audio.

Enough Rambling, lets get to the design!
As mentioned, my initial goal is VRC6 support, the wiki does a good job of explaining it's operation so the discussion mostly assumes the reader has that entry knowledge. I'll do my best to explain how VRC6 register values translate to mcu variables to help explain operation. On a high level the mcu is executing the synth code on a fixed periodic basis in a interrupt service routine (isr). This happens to be the PWM DAC overflow interrupt for me currently, but regardless of the DAC type some method of time keeping is needed and the synth code is being executed on a periodic basis. This gives the first necessary variable:

T_tune = T_isr / T_hardware_synth: This is the number of how many hardware synth cycles occur for each soft synth isr. The STM8 runs @ 16Mhz +/- 1% assuming an 8bit PWM DAC the timer's top value is 255, so the soft synth isr occurs every 256 STM8 cycles (62.5nsec) = 16usec. VRC6 runs @ 1.79Mhz, T=558.7nsec. T_tune = 16usec / 558.7nsec = 28.64
This value is stored in a 16bit variable I've chosen 12.4 fixed point to align with subsequent variables. This variable can be adjusted to tune the synth with 1/16 step size, with T_isr of 16usec that gives 0.2% tuning steps.

Chan_count: This is the period counter for the channel, it gets reloaded to the current register value when it decrements past zero. The VRC6 has 3 channels @ 12bit, so we'll need 3 of these registers too. Aligning with T_tune above, I'm using 16bit variable with 12.4 fixed point. One thought I had for the lower fractional bits is to allow the main thread to add/subtract 'random' amounts to this register after each rollover to reduce aliasing. Not sure 4bits is enough to matter, but it's effectively all that there is to spare w/o 32bit mcu.

Chan_period: This is the current value stored in the synth's register which gets copied over at each rollover. For VRC6, these registers hold the actual value currently in $9001/2, $A001/2, $B001/2 registers. Must be at least 16bit, saves time to store as 12.4 fixed point so the 4bit shift only has to be performed once when the register is written to.

WAVE_TABLE: 64-256 entry wave table ram implemented in an array of bytes. The max value VRC6 needs to store in this table is 6, so in reality only 3bits are needed per entry. But condensing two nibble entries per nibble complicates data processing. VRC6 also doesn't really need 64 entries, but this is a good round number to start off with. Using byte variables to point to the current entry in selected in this table makes expansion up to 256 entries easily feasible. STM8 has 1KB of SRAM, dedicating 25% of SRAM to wave tables is within reason.

Chan_ptr: This is pointer to the current index in use by the channel in WAVE_TABLE above. Implemented as a single byte, limits WAVE_TABLE to 256 entries.

Chan_start: This is the starting value of Chan_ptr, this value gets copied to Chan_ptr when it exceeds Chan_last below.

Chan_last: This is the max Chan_ptr value for the channel.

Chan_vol: single byte variable, this equates to the standard volume registers in the hardware synth. If there's need to adjust/shift the volume register for leveling purposes, doing so when written to saves on subsequent computations.

Chan_out: single byte variable, final calculated output of the channel for the current isr cycle.

DAC_out: doesn't necessarily count as a variable, this is simply the value written to the DAC to be output for the current isr.

So for VRC6, with the variables above, each channel requires 9 bytes x 3chan = 27Bytes. Plus 2Bytes for T_tune and a few other hardware register values that aren't accounted for consumes ~30Bytes + WAVE_TABLE ram. Some of these variables will benefit from being stored in STM8 zeropage, but doesn't matter much for most.

VRC6 of course doesn't need wave table ram, but if we initialize the WAVE_TABLE in a way that aligns with the VRC6, we can easily use it to replicate the function of the 2x square channels, and saw channel along with the duty cycle generator. My thought is to initialize the first 16 entries [0-15] to 0x01, and next 15 entries [16-30] to 0x00.

The Chan_start variable gets set based on the selected duty cycle, effectively Chan_start = 15 - D. And Chan_last = Chan_start + 15. So for VRC duty setting of D=0 (6.25%) Chan_start = 15, Chan_last = 30. The falling edge of the square in the WAVE_TABLE occurs between index 15-16. Thus giving the proper 6.25% duty cycle in practice. Yes, the wave table is a bit overkill for a simple square; but it works and doesn't need to cost much for computation. Again it's over kill, but when VRC6 M is set for 100% duty, Chan_start simply gets set to 0, and Chan_last = 15. Just as the square duty cycle generator has 16 steps, the square channel steps through 16 entries of WAVE_TABLE per period.

To replicate the VRC6 saw wave, a series of 14 step entries can be used: 0-1=0x00, 1-2=0x01, 3-4=0x02, ... 12-13=0x06. To keep from colliding with the square table, these entries would be well placed at WAVE_TABLE[32-46], but obviously other locations would work just as well. For the saw channel we simply fix Chan_start = 32, and Chan_last = 46. And here just as the VRC6 saw channel has 7 steps, but is only incremented every other cycle, effectively dividing by 14, we've replicated the same functionality with 14 entries of the WAVE_TABLE.

To put everything together, here's pseudo/C code for the entire soft synth isr:
Code:
softsynth_isr:
Ch1_count = Ch1_count - T_tune
if (Ch1_count < 0) {
   Ch1_count += Ch1_period
   Ch1_ptr ++
   if (Ch1_ptr > Ch1_last) {
      Ch1_ptr = Ch1_start
   }
}
Repeat above for each channel
Ch1_out = Ch1_vol * WAVE_TABLE[Ch1_ptr]
Ch2_out = Ch2_vol * WAVE_TABLE[Ch2_ptr]
Ch3_out = Ch3_vol * WAVE_TABLE[Ch3_ptr]
VRC6 requires further manipulation of Saw output:
   Ch3_out = (Ch3_out && 0x00FF) >> 3
DAC_out = Ch1_out + Ch2_out + Ch3_out


In the main thread, some tasks may include:
Copying newly written values over to synth variables.
If implementing some sort of gaussian anti-aliasing effort tweak Chan_count if rollover occured last cycle.
Maybe adjust T_tune if synth needs tuned.
Perform any other calculations that are 'nice to have' but not necessary.

There are a few features/nuances of the VRC6 which haven't been accounted for, but it's not hard to imagine how they might be implemented without changing the isr itself. Biggest features being x16/x256 frequency flags that effectively shift period registers by x4/x8 bits. I think the x16 shift makes more sense to shift T_tune to the left 4bits. If the x256 flag is set then additionally Chan_period would be shifted bits to the right. Both of those shifts can be reversed without data loss when the flags are cleared. Beyond that care needs to be taken to assure proper functionality when channels are enabled/disabled, duty cycle generators reset, etc. But these are all things that should be easily handled in the main thread, and don't need to run each soft synth isr cycle.

For completeness, here's a draft of STM8 assembly needed to implement the above psuedo/C code to get a sense of cycle count.
Code:
isr_TIM2_update:           ;ISR requires 9cycles for interrupt + 2cyc for jump to ISR + 9cyc return from interrupt..  20cycle ISR overhead.
   LDW   X, Ch1_count    ;2cycles
   SUBW X, T_tune          ;2
   LDW   Ch1_count, X    ;2
   JRPL   next_channel    ;1/2
;Channel update takes 8cycles if no rollover of Ch1_count
   ADDW X, Ch1_period  ;2
   LDW   Ch1_count        ;2
   INC     Ch1_ptr            ;1
   LD      A, Ch1_ptr        ;1
   CP     A, Ch1_max       ;1
   JRPL  Ch1_output     ;1/2
   MOV   Ch1_ptr, Ch1_start ;1
;Channel update takes 16cycles if Chan_count rolls over to step Chan_ptr plus output updates below

Ch1_output:
   LDW   X, Ch1_ptr        ;2cyc     Reserve empty byte for MSB!
   LD      A, (WAVE_TABLE, X)   ;1
   LDW   X, Ch1_vol        ;2          Reserve empty byte for MSB!
   MUL    X, A                   ;4
   LDW   Ch1_out, X        ;2
;Square Channel updates take 27 cycles if Chan_count rolls over
   ;being a square, optimizations could be made to remove multiply above and use TNZ (WAVE_TABLE, X) instead saving 7 cycles..

next_channel:
   ;same calculations as above repeated for remaining channels
   
;saw channel requires all the calculations above for Ch1/2 plus extra volume adjustments:
   LD     A, XL                   ;1
   SRL   A                         ;1
   SRL   A                         ;1
   SRL   A                         ;1
   LD     Ch3_out, A         ;1
;Saw wave requires 32 total cycles to update if Chan_count rollover occurs

Sum_channels:
   LD     A,  Ch1_out       ;1
   ADD   A,  Ch2_out       ;1
   ADD   A,  Ch3_out       ;1
   LD     DAC_out, A        ;1
;final output to DAC = 4 cycles fixed

;extra master volume leveling/adjustments may be desired.

;TOTAL ISR cycle count:
;minimum (no rollovers) = 20 + 8 * 3 + 4 = 48cycles
;min + square rollover = 48 + 19 = 67cycles
;max possible all 3chan rollover = 48 + 19 * 3 + 5 = 110cycles
;8bit PWM DAC with top value of 256 would execute this ISR every 256 CPU cycles
;min utilization (no rollovers) = 48/256 = 19%
;nominal util (one rollover) = 67/256 = 27%
;max util (all rollover) = 110/256 = 43%


In the end things are looking pretty decent. I initially hadn't considered that channel calculations can be significantly reduced if the Chan_ptr isn't incremented as the last calculated output is still valid. Not every case is covered in this first draft however as things like volume changes won't take effect until the next Chan_ptr increment; I suppose that delay wouldn't be a real issue in practice.

Looking ahead to Sunsoft-5B audio there's only a few features that would need added to support the noise channel and envelope generator. Sunsoft-5B square channels are simpler though without a duty cycle generator. I wouldn't necessarily expect it to be reasonable for a resource constrained mcu to be able to fully implement VRC6 & Sunsoft5B at the same time. But the overall soft synth design could be modified to do so. In the end this model allows the more complicated mcu soft synth act as a standard synth to gain tool/emu support. At the same time the wave table and all the variables to go along with this homebrew soft synth are sitting there ready to be utilized by any motivated individuals. If nothing else this gives me a good soft synth template to start from when working on projects where there is only interest in a specific replica.

As always, I'm interested in thoughts, feedback, and ideas you all have on my scheme here!
Re: versatile microcontroller based soft synth design
by on (#206921)
Your wavetable structure looks a lot like how the Namco 163 already operates. The difference is that the N163 packs a pair of 4-bit samples into low and high nibbles of each of its 128 bytes of RAM, and pitch is set based on frequency as opposed to period.
Re: versatile microcontroller based soft synth design
by on (#206929)
I need to take time to look over the post, but I did at least want to confirm that the NT, NT Mini, and I'm pretty sure the AVS also, all support expansion sound on the NES slot. They use an ADC to digitize the audio for output over HDMI.
Re: versatile microcontroller based soft synth design
by on (#206937)
It sounds like you could use it as VRC6 "out of the box", but then apply changes if you want to to fine-tune the sound palette to your homebrews' needs? For example, a more mellow saw, altered pulse widths, or something completely else. Such static changes should be easy to apply and doesn't necessarily require direct feedback in famitracker. It could be seen as the "last touches".
Re: versatile microcontroller based soft synth design
by on (#206968)
Quote:
Your wavetable structure looks a lot like how the Namco 163 already operates. The difference is that the N163 packs a pair of 4-bit samples into low and high nibbles of each of its 128 bytes of RAM, and pitch is set based on frequency as opposed to period.
Yes there was a fair amount I gleaned from N163 when designing this setup. The wave table arrangement isn't necessarily different as this wave table could be presented to the NES is the same addressing/endianess as N163; that determination is up to how the mapper makes the wave table accessible to the NES.

The frequency & phase accumulator setup of N163 is inherently well setup for mcu replication since each channel has fixed computation time. It would be possible for this design to be made compatible with N163 by doing some math to convert frequency register values to the needed period equivalent. However things would really become a mess quickly especially with end frequency being dependent on number of enabled channels. In the end this design would have to be mostly abandoned to exactly replicate N163. It may be better if the mcu was solely dedicated to audio synthesis. The 15 6502 cycles the N163 uses to process each channel would equate to ~133 mcu cycles @16Mhz, and ~400 mcu cycles at 48Mhz which would both be more than enough time to perform necessary calculations. The audio synthesis routine would also have to be separated from the DAC output if using PWM by internally mixing all channels.

I would like to replicate N163 at some point, but it'll require a separate frequency-phase accumulator based soft synth definition as this period based definition is effectively incompatible.

Quote:
the NT, NT Mini, and I'm pretty sure the AVS also, all support expansion sound on the NES slot. They use an ADC to digitize the audio for output over HDMI.
Good to know. I hadn't thought about how these are re-digitizing the cart's audio output. I expect this would be a pretty significant problem when the cartridge utilizes a PWM DAC. I expect the periodic PWM noise combined with the periodic sample rate of the ADC will turn into an aliasing nightmare. Oh the irony...

Quote:
It sounds like you could use it as VRC6 "out of the box", but then apply changes if you want to to fine-tune the sound palette to your homebrews' needs?
Yes that's the idea exactly. If it ends up gaining enough popularity perhaps emulator/tracker developers will be motivated to add compatibility. Gotta build it before they'll come.. In the end it doesn't matter a whole lot to me whether emulator/tracker support for the underlying synth definition I'm creating. The project can succeed well enough configured to function as traditional synths alone, anything beyond that is gravy.
Re: versatile microcontroller based soft synth design
by on (#206969)
infiniteneslives wrote:
There are a few features/nuances of the VRC6 which haven't been accounted for, but it's not hard to imagine how they might be implemented without changing the isr itself. Biggest features being x16/x256 frequency flags that effectively shift period registers by x4/x8 bits.

Don't bother with it. This is a useless feature of the VRC6 anyway. Even if you want to support repros, all extant games write 0 to $9003 and forget about it. (I don't think most emulators even implement $9003 for it.)
Re: versatile microcontroller based soft synth design
by on (#206971)
infiniteneslives wrote:
T_tune = T_isr / T_hardware_synth: This is the number of how many hardware synth cycles occur for each soft synth isr. The STM8 runs @ 16Mhz +/- 1% assuming an 8bit PWM DAC the timer's top value is 255, so the soft synth isr occurs every 256 STM8 cycles (62.5nsec) = 16usec. VRC6 runs @ 1.79Mhz, T=558.7nsec. T_tune = 16usec / 558.7nsec = 28.64
This value is stored in a 16bit variable I've chosen 12.4 fixed point to align with subsequent variables. This variable can be adjusted to tune the synth with 1/16 step size, with T_isr of 16usec that gives 0.2% tuning steps.

An 0.2% resolution of tuning is about 3.4 cents, which is pretty good. I think the VRC7 does a bit worse than this. (Is the MCU's timing reliable or does it drift? You should add its potential variance to this tolerance...)
Re: versatile microcontroller based soft synth design
by on (#206972)
infiniteneslives wrote:
I hadn't thought about how [HDMI clone consoles] are re-digitizing the cart's audio output. I expect this would be a pretty significant problem when the cartridge utilizes a PWM DAC. I expect the periodic PWM noise combined with the periodic sample rate of the ADC will turn into an aliasing nightmare. Oh the irony...

It might end up not a problem. The console has to put an appropriate low-pass filter between the cart edge and the ADC, or aliasing from authentic N163 carts will sound even worse than it already does.
Re: versatile microcontroller based soft synth design
by on (#206994)
The plan looks really solid, then :) Kickstarters could always bundle in your dongle as an option, or a resistor which comes near-free. Slight deviations in sound doesn't matter to homebrew, because that will become the sound of said software. True, emulation might be a bit off, but for cartridge oriented releases the ROM image tends to be a bonus anyway, and you can get an accurate audio representation using something like gradual games socket application for steam/PC.

And IMO, reproduction should be a home tinkerers' hobby. To a hobbyist, i think having slightly divergent sound should still be reasonable and better than not having any option at all.

===
As for expanded possibilities... Feel free to disregard this if it is too far outside the scope (i suspect so), but suboctave generators are really cheap on logic (comparator + D flip flop). Assume one suboctave per channel: If there was an interface that could set their volume (0 is off) to a subset amount relative to their mother channels' volume (eating some more resources, here..), that'd be quite an expansion of sonic possibilties without too much programming game driver-side.
Re: versatile microcontroller based soft synth design
by on (#207013)
FrankenGraphics wrote:
As for expanded possibilities... Feel free to disregard this if it is too far outside the scope (i suspect so), but suboctave generators are really cheap on logic (comparator + D flip flop). Assume one suboctave per channel: If there was an interface that could set their volume (0 is off) to a subset amount relative to their mother channels' volume (eating some more resources, here..), that'd be quite an expansion of sonic possibilties without too much programming game driver-side.

That logic is for an analog circuit, though, which doesn't quite correlate to digital synthesis directly. (Though for a square wave only it'd be pretty close.)

If you were using an N163 you could already do exactly what a suboctave pedal does just by using a waveform that is twice as long: people can and do create stacked-octave waveforms for N163. I think that's probably the simplest way, and more versatile too (you're not limited to that flip-flop logic, you can use any waveform).

IMO the N163 is actually simpler hardware with more utility than VRC6, but I think its reputation was a little bit tainted by people using 8 channels instead of 4.
Re: versatile microcontroller based soft synth design
by on (#207019)
Yeah, i only figured the software application would scale to a similar size because of the simple elements. By contrast, the simplest form of the ring modulator effect (achievable passively with 4 diodes and 2 transformers) has no such simple software counterpart, AFAIK.

I also just realized the comparator (or its software counterpart) is completely unnecessary for anything pulse/square-like. That's for telling what's what in more dynamic waveforms (sinoids, tris, or anything else changing amplitude progressively).

But i didn't think of achieving the same with wavetable synthesis despite having done so myself quite a bit in the past :roll:

I guess the remaining benefit is separate volume control over the suboctave from the wavetable control, in other words: not worth it.

I also thought about a general purpose controllable aux tap, analog-side. Stuff would be sent to the aux out, which is an unsoldered pin hole. Then you could add in anything you'd like, for example a bucket brigade delay chip. From there, another unsoldered pin hole would go to the analog sum/mix.

It's equally far away from the original idea, but i might aswell mention it. It might not even be possible given the limited number of out pins of the chip.
Re: versatile microcontroller based soft synth design
by on (#207024)
Quote:
Don't bother with [x16/x256 frequency flags]. This is a useless feature of the VRC6 anyway.
I kinda wondered about that, seems like a pretty useless feature as it only shifts the period register.

Quote:
(Is the MCU's timing reliable or does it drift? You should add its potential variance to this tolerance...)
So this is technically outside the scope of the synth's definition as it depends on the selected mcu & clock source. When using an mcu's internal oscillator this can be a concern though, so that's why I wanted some means for tuning the synth itself. Some mcus have very accurate internal self calibrating oscillators especially when featuring crystal-less USB. Of course another option is to simply use an external crystal as the mcu clock source. But for low cost solutions one will want to make the internal RC oscillator work.

In the case of the STM8 it's internal RC oscillator is factory trimmed in 0.5% steps, it's "guaranteed by design" to be 1% accurate at 5v & 25C. I question how sensitive it is to external supply voltage as the core is internally regulated down to 1.2v, I presume the oscillator is as well. If that's the case the biggest sensitivity is temperature. Would be worthwhile to take measurements in the range of acceptable room temperatures, and see how much self heating occurs during operation. It practice, I would guess that any drift would be due to self heating of the die due to operation.

There's a multitude of ways that one could tune the synth by tweaking the T_tune value, that all really depends on the implementation and mapper setup. One of the simpler/cheaper means would be to have the timing accurate 6502 make a sequence of timed writes to a specific register. Perhaps doing that at startup would be enough, if drift were an issue maybe it should be performed as often as prior to each new track/level. Might even be cool for something like a chiptune album to have a tuning screen where the user can tune the synth manually while the same note are played by APU & soft synth. For mcus dual tasking as a CIC, there may be options for self calibrating to the CIC clock/transfers although I'm not sure how accurate the CIC clock is.

Quote:
suboctave generators are really cheap on logic (comparator + D flip flop). Assume one suboctave per channel: If there was an interface that could set their volume (0 is off) to a subset amount relative to their mother channels' volume (eating some more resources, here..), that'd be quite an expansion of sonic possibilties without too much programming game driver-side.
My intent for this synth definition is to keep everything within the soft synth itself, so adding external logic/hardware is outside the scope. I don't really understand how this proposed suboctave generator would behave. My guess is that it would be something that could be integrated into my soft synth idea but I'd need to have it's behavior better explained. Rainwarrior's N163 proposal sounds like it could easily be implemented in my current wave table design too. Most of your recent post is over my head unfortunately so I don't have much to comment on that.


Looking ahead to Sunsoft-5B compatibility, I do have some ideas & a question on the noise frequency you guys can probably help out with.

Evenlope Generator:
I've mostly wrapped my head around how this works. To implement it in this soft synth design my thought is to have the envelope generator run just as all the other tone channels. Just need a few different logrithmic wave forms loaded to select from, and the selected shape would determine the starting Chan_ptr, Chan_start, & Chan_last.

This isn't the most efficient setup, but its simple and there's currently WAVE_TABLE entries to spare. I can imagine a much more condensed version of this, but it would complicate the calculations of which index the envelope Chan_ptr gets set to.
Code:
Fill WAVE_TABLE with overall shape: /¯_/\_\¯
8 'slots' 32 entries each
slot0 0-31: log up slope
slot1 32-63: all high
slot2 64-95: all low
slot3 96-127: log up slope
slot4 128-159: log down slope
slot5 160-191: all low
slot6 192-223: log down slope
slot7 224-255: all high


Code:
Value      Cont  Atk  Alt  Hold  Shape     Chan_ptr: init start last
$00 - $03    0    0    x    x    \_______             128  160  191
$04 - $07    0    1    x    x    /_______             96   64   127->95  (max would need adjusted after cycle)
$08          1    0    0    0    \\\\\\\\             128  128  159
$09          1    0    0    1    \_______             128  160  191
$0A          1    0    1    0    \/\/\/\/             128  96   159
$0B          1    0    1    1    \¯¯¯¯¯¯¯             192  224  255
$0C          1    1    0    0    ////////             96   96   127
$0D          1    1    0    1    /¯¯¯¯¯¯¯             0    32   63
$0E          1    1    1    0    /\/\/\/\             96   96   159
$0F          1    1    1    1    /_______             96   64   127->95  (max would need adjusted after cycle)

Designing out loud here, but with this initialization of WAVE_TABLE, and translation of shape bits to Chan_ptr init, start, and max allows my current design of a channel to act as a YM2149F. The exception is the /_ shape which would require max to be adjusted after the first cycle. While code could be added to cover this case, a better option would be to trim the wasteful slots 5 & 7 to just a byte or two as that's all that would be needed. That would save ~60Bytes make room for a dedicated /_ shape as it would only need ~34Bytes; leaving ~26Bytes of WAVE_TABLE ram to spare..

Since Sunsoft5B runs at half the speed, T_tune would get halfed to 14.32. The square channels could utilize slot 1 & slot 2 in a similar fashion to how the VRC6 squares did. So there would be 3 square channels and the envelope generator would make a 4th channel. The square channel output code would have to change to support using the current envelope level instead of Chan_vol.

The square channels would utilize 16 entries of the WAVE_TABLE to get the necessary /16 frequency. The envelope generator would use the 32 steps per ramp and thus require additional division by 8 to get proper frequency. The EG has 16bit period register which doesn't fit my 12.4 setup. So in the end some special cases are still going to need to be created to support the EG. Perhaps it would be okay to simply only process the EG 'channel' every 8th isr.. That could certainly help cut down on total processing time. My 'versatile' design here suddenly doesn't seem so versatile, but perhaps I shouldn't be considering the EG like the other tone channels. Just need to think harder about the best way to generically add on an envelope generator. Oh well, let's move on for now..

Noise Channel:
This is something not well supported by a wave table, but thankfully implementing a 17bit LFSR in with the mcu would only take a few cycles. The only thing I'm not quite sure about is what the "frequency" of noise is exactly.
Quote:
The noise generator produces a 1-bit random wave with a period controlled by the CPU clock and the 5-bit period value in register $06.
- Frequency = Clock / (2 * 16 * Period)

My confusion is because there is no defined wavelength for noise. Is the wavelength simply the time 1-bit is active? The noise generator shifts the next bit of noise in/out the LFSR each period? That's my best guess, just want to confirm it's not somehow every 2bits or more; or somehow a period is a full cycle of the LFSR.

So the noise generator clock has a period of 558.7nsec (1.79Mhz) * 2 * 16 = 17.88usec which is then multiplied by the Noise Period register to get the final "period of the noise". Assuming soft synth isr is running every 16usec, and the noise period register is only 5 bits perhaps it would make sense to use something like 6.10 or 8.8 fixed point for the mcu's noise_period variable. And then subtract "T_tune_noise" 16/17.88 = ~0.895 each soft synth cycle. We're pretty close to executing at the actual frequency of the noise channel @16Mhz/16usec. If the EG is only running every 8th isr, then perhaps it one could only have the noise generator run every other isr cycle. So the EG would run isr cycle number #0, and the noise gen would run on odd cycles for example. In that case the T_tune_noise would double = ~1.79. It is just noise after all...

Another idea would be to give the EG it's own T_tune_env which equated to being ran every other cycle. The EG could run on even cycles and noise on odd for example.

Going through this exercise has me thinking an mcu like the STM8 @ 16Mhz will start to get pushed to it's limits with a full featured Sunsoft-5B. I would guess nominal execution time to be somewhere on the order of 100cycles, maybe there's hope to keep max isr time below 200 cycles. Perhaps things could work well with cutting the soft synth isr down to 31.3Khz (512 cycles) though. The extra processing power of a 48Mhz cortex M0 starts to look appealing especially if it comes with a hardware R2R DAC.

Quote:
IMO the N163 is actually simpler hardware with more utility than VRC6, but I think its reputation was a little bit tainted by people using 8 channels instead of 4.
Yes and it's method of devoting processing time to each channel certainly suits an mcu replica well. The switching noise of the original is easily avoided by internal summation mixing. Considering all these things I'm not sure my idea of one versatile soft synth definition to rule them all makes the most sense. There's always going to be some additional features needed to mimic one synth verses another. But I've got to start somewhere and this discussion has already proved helpful.
Re: versatile microcontroller based soft synth design
by on (#207025)
Quote:
The noise generator shifts the next bit of noise in/out the LFSR each period? That's my best guess, just want to confirm it's not somehow every 2bits or more; or somehow a period is a full cycle of the LFSR.
Yes, it's the bit emission frequency.

infiniteneslives wrote:
thankfully implementing a 17bit LFSR in with the mcu would only take a few cycles. The only thing I'm not quite sure about is what the "frequency" of noise is exactly.
An LFSR with user-controlled prescaler is close enough to a white noise source with a first-order lowpass filter, and the frequency of emitting a new bit is the corner frequency of the lowpass.

Er. As long as the playback rate is substantially lower than the total repeat time of the LFSR. e.g. the NES's wave channel is 15bit, and one of the playback rates is 447kHz; when that's used you can hear a 14Hz rhythm.

For particularly short maximal-period LFSRs (e.g. 3,4,5,6 bit), the repeat frequency of the LFSR becomes the important part: in this case, it's instead musically equivalent to a very narrow duty cycle pulse wave. (e.g. a 3-bit maximal period LFSR sounds like a 1/7th pulse wave. 4-bit, 1/15th; 5-bit 1/31st, &c)
Re: versatile microcontroller based soft synth design
by on (#207033)
For 1-bit noise generator the samplerate has a pretty big impact. Like, when emulating the NES audio at e.g. 48kHz it's the one thing that most needs oversampling to sound correct. Without oversampling, basically the top ~5 noise frequencies will sound the same i.e. once you exceed your bandwidth it's all just the same white noise.

When oversampled they do still have more or less the same audible spectrum, I think, just there's a falloff in amplitude? So it's maybe not that important unless you're trying to emulate an existing high-frequency 1-bit noise implementation.

infiniteneslives wrote:
I don't really understand how this proposed suboctave generator would behave.

The effect is basically just that the audio signal is used to clock a flip-flop, and then that flip flop is used to invert that same input as output. For a square wave input, this outputs a square wave at half the frequency (octave down). For anything else, it's roughly some kind of octave down sound, but the resulting waveform is not entirely intuitive. That's the basic idea, anyway. (There's a bunch of other considerations, e.g. bandlimiting of the clock.)

infiniteneslives wrote:
5B waveforms

I always figured the envelope waveforms of the AY were generated just by sending the internal phase counter directly to the volume control of the channel (optionally inverted). I'm not sure if using a wavetable would actually make that easier?

Also, a thing that's weird about the 5B is that it's got a logarithmic DAC, rather than linear. The balance is a bit annoyingly coarse because of that, though it's actually fantastic for implementing fade-out. I suspect that its amplifier also clips the signal if it gets loud enough but I haven't got around to measuring this yet.
Re: versatile microcontroller based soft synth design
by on (#207042)
rainwarrior wrote:
For anything else, it's roughly some kind of octave down sound, but the resulting waveform is not entirely intuitive.

This is often the desired effect of a suboctave generator - to get a somewhat pulse-like waveform one octave below based on something not so pulse-like. The fact that it may mistrig on more complex waves causes a warble/warp kind of effect, to animate the sound. Of course, if the purpose is getting an exact replica one octave down, then it's no good.