426

(37 replies, posted in Other Platforms)

Alright, I examined the most CPU time consuming line of the code and possible changes in it, and you know, somehow I chose the most optimal approach intuitively right off the bat. Any changes (trying to change the logic, order of condtion, playing around with temporary local register variables) only make it longer for an opcode or two. Here is the line and the code:

  if(cnt_load[0]) { --cnt_value[0]; if(cnt_value[0]==255) { output_state=SPEAKER_BIT; cnt_value[0]=cnt_load[0]; } }

 1ba:    20 91 08 01     lds    r18, 0x0108    ; 0x800108 <cnt_load>
 1be:    22 23           and    r18, r18
 1c0:    59 f0           breq    .+22         ; 0x1d8 <main+0xb4>
 1c2:    80 91 00 01     lds    r24, 0x0100    ; 0x800100 <_edata>
 1c6:    81 50           subi    r24, 0x01    ; 1
 1c8:    80 93 00 01     sts    0x0100, r24    ; 0x800100 <_edata>
 1cc:    8f 3f           cpi    r24, 0xFF    ; 255
 1ce:    21 f4           brne    .+8          ; 0x1d8 <main+0xb4>
 1d0:    90 93 10 01     sts    0x0110, r25    ; 0x800110 <output_state>
 1d4:    20 93 00 01     sts    0x0100, r18    ; 0x800100 <_edata>

Of course pure assembly code version can be made more efficient, by storing counters directly in registers and checking for carry rather than 0xff, but as for the C appraoch, not much can be improved.

427

(37 replies, posted in Other Platforms)

Here is the ELF file. I figured out how to find it, the worst thing about it was that the location is different with every build. So every time when you need to analyze the code, you have to manually copy/paste the path from the IDE verbose output. Also found the objdump (had it ony my PC, actually), and was able to see the assembly code, but I'll leave analyzing it and finding possible optimizations for later. At the first glance it seemed pretty effective, sans the tons of push/pops on the interrupt entry/leave.

Please note again, that's not my original song in the example. I can't recall who gave it to me and if he asked to not publish the XM module. So if anyone wants to provide his song to be distributed with this code (with proper credit), it would be cool.

428

(37 replies, posted in Other Platforms)

Yeah, I used the latest IDE, 1.8.1, and I have read that there is inconstince with the PROGMEM between versions.

I don't have problems other than that it takes ages to figure out and find everything. Like finding where the damn ELF file is located, where to get the avr-objdump without installing a pile of unneeded things, etc. I just need time to devote to this instead of more important things. Thanks, I'll try the avr-objdump sometime later.

429

(37 replies, posted in Other Platforms)

I don't know, but even so, I'm afraid it would only complicate things. Like, you would have to compile the library in one tool, then move to Arduino IDE and compile test from there. For end user, it would lack the main quality of the Arduino platform, being able to easily see and modify code. The only benefit of using Arduino that would remain is the on-board USB programming. Maybe there are reasonable use cases for this approach, though.

At the moment I don't really have ideas where to go next with Arduino. Maybe port some other engine(s), maybe explore possibilities with native engines. Sure nothing big that would take large assembly code parts. For 100-200 line pieces inline asm is tolerable.

430

(37 replies, posted in Other Platforms)

That's the problem, there is no easy way to see the assembly code output. People on forums mostly recommend to use a disassembler. I decided to not bother with it for start, although to get more interesting things done in C it surely will be needed, so I will have to figure it out later.

I love the AVR assembly too, and sure would prefer it over C/Arduino for this kind of projects. However, there is a tricky choice, considering that Arduino is very popular and easily accessible, but not really suitable for programming in assembly, while 'bare' AVR (custom schematics or existing dev boards) is way less popular and accessible, but allows to unleash the power of the chip.

In general, I think that Arduino has some potential to be used as an entry level modern platform for 1-bit music, to some extent. But for anything serious I'm sure will go with just bare AVR chip.

Funny enough, unlike Arduino, I don't have issues with either ISP or a parallel programmer on the very same PC (but I believe there could be some issue with the USB controller indeed, had issues with a USB sound card).

431

(37 replies, posted in Other Platforms)

Alright, I needed some place to start with exploring the possibilities of the Arudino and AVR MCUs application to the beeper music, and after some thoughts chose to start from the simplest thing possible - recreating the original Octode engine for Arudino Uno, in C. A very special kind of fun, should I say.

The engine port is nothing special. I decided to stick to the 8-bit counters, and kept the same sample rate as the original to avoid changes in the note range, so it has the same sound quality. I didn't use the Arduino hardware PWM stuff, although it can be done quite easily, and I think it may give some good improvements. The clicky drums algorithm is not 100% match to the original, but sounds more or less along the same lines.


The issues I've had with this small project were mostly related to the Arduino platform itself, not programming. I admit and appreciate its convinience for rapid prototyping, and use it time to time, but don't really enjoy it, because somehow it always full of really weird issues on my side, with no real solution to be found anywhere. For one, I can't just upload a sketch to any of the boards I have (a few different kinds), as it constantly fails with all kind of error messages. Attempting to do that a few times in row, maybe a few tens of times even, solves the problem, eventually sketch will upload successfully. Randomly mashing the Arduino reset button during this increases chances of luck. Another issue is that while Arduino is connected to the PC, it often happens that it starts to constantly reset on its own every ten seconds or so, at random times. So sometimes it is hard to tell whether the sketch code has issues, or it is just Arduino feels funky. Luckily, powering it from a stand alone 5V source makes it work stable.


Code wise, some other issues came from the fact that C compiler is more high level than assembly. Of course I could just write sound generation parts in assembly code, but I wanted to stick to C in order to make the thing more accessible for beginners, hoping that someone may eventually get interested in making 1-bit stuff with Arduino and will need simple examples to learn on them.

It is difficult (haven't found an easy way yet) to see the assembly output of the C compiler in Arduino environment, and it may eventually change after some compiler updates anyway, so consciously designed timed code is pretty much out of question, although it is possible to make something like that with a good deal of guess work and measurements. A more reliable way to go is to use the timer-driven interrupts that can fire up as often as a few tens to a hundred KHz, and in case when different pulse width is needed the hardware PWM unit should be utilized. This makes Music Box-like pulse interleaving and Fuzz Click-like PWM/PFM engines difficult to implement - something is possible, but inconvinient under these circumstances. That's why I chose Octode, as it only have one sample output per loop.

There is a way to guesstimate how much CPU time a code piece takes without extra tools or profiling, by compiling the code, remembering how many bytes it takes, then deleting a line, compiling again, and comparing the sizes. Almost all but few AVR opcodes are two bytes long, and mostly take 1-2 cycles, sometimes 3. So, guessing that average opcode timing is 1.5 cycles, the size difference multiplied by about .75 should give a rough idea. Could be useful to plan how to not exceed the time available in the timer interrupt, which is for this engine port is about 16000000/12727=1257 cycles (Arduino clock divided by sample rate).


Another inconvinience in porting from assembly code to C is that code in beeper engines very often relies to the carry flag. Like, in assembly code you can simply increment or add to a register, and execute extra code only if the result exceeded the register capacity, with the result value wrapping around properly by itself. There is no way in C to mimic that with equal efficiency, without increasing width of a variable by a byte or adding more conditions. For one, when you add a value to variable and want to detect carry, it may look something like this:

n=counter+value;
if(n<counter) { /* carry will happen, new counter value in n */ }
counter=n;

Shifting out a bit out of a register also takes extra code or rethinking the algorithm, like remembering the bit before shift. This makes already not very efficient compiled C code to bloat further. Luckily, Arduino seem to have enough power to run C ports of any ZX Spectrum beeper engine, written in C as efficient as it gets with straight rewrite, but to make more advanced ones in C some common code tricks and general design ideas likely going to be developed.


To convert music data I modified my recently written, specifically for this project, Python port of the XM to Octode converter. For simplisity reason data format remained the same as in the original Octode, although Arduino has enough power to allow much more efficient format with packing. Can't recall who and when (~2011) gave me the XM file of the song I'm using for testing, Mister Beep I guess? Not publishing the source module just in case.

There was an issue with putting ~16K of music data into the memory. By default Arduino copies all arrays and variables into the RAM, even large constant ones. That's because of the AVR's Harvard architecture, with separate memory systems for code (Flash ROM) and data (RAM). Compiler is simply not aware of possibility to put data into program memory. There is a way (different between Arduino IDE versions) to mark arrays and variables with attribute so compiler will put them into program memory. However, this data can't be just accessed by a pointer. Special functions has to be used instead. In AVR assembly code it is similar, but easier and efficient, just another opcode to load from program memory.


Overall player architecture is very simple.

There is a timer interrupt that calls its handler at sample rate (12727 Hz), that's basically sound synthesis part. Handler outputs last calculated output bit to the speaker pin (Port D, bit 7 by default) before of all, to avoid jitter. Then it checks whether the synth is in tone or clicky drums mode. For tone mode it calculates all eight counters just like the original Octode does. I used unrolled code there both for speed and clarity. Constant array indexes seemingly not affecting the code size or speed. For clicky drums mode it calculates a bit for percussive timbre using a set of counters, which is a bit different to the algorithm used in the original Octode. The handler keeps track of the drums mode with a counter. When player needs to play a drum, it simply sets the counter, and while counter ticks down each handler call, drum mode is active. This allows to avoid the need of tempo compensation, drums cause no tempo fluctuation, and can have any reasonable duration. Interrupt handler also decrements another, 16-bit counter until it gets zero. This counter is used to keep sync with the player.

Song data parsing and playing happens in the main thread. Once a row is parsed, player sets the sync counter to the speed value, counted in sample rate units. Then it just waits until the interrupt handler will decrement it down to zero, and next row gets processed. One major weird issue I had was related to this part. Initially I waited for the counter to be zero with just while(counter);. This caused major tempo fluctuations, with both speed increase and decrease. Once I changed it to while(counter) delay(1); it started to work, although the 1 ms delay may change the tempo a bit. Still not figured why the original approach didn't work as expected.


Sorry, no sound example yet, I need to set up a way to record it directly, with proper quality. At the moment I'm using an old transistor radio as an amplifier with speaker for testing.

Thanks a lot, I wouldn't even think that the bottleneck is the array allocation. I changed the script a bit, replaced multi-dimensional list with one-dimensional, and now it takes just a second to convert a module, which is good enough, I think.

The improved version re-attached to the first post.

A little project I just made - a small Python library to parse XM modules and extract song data from them easily. Comes with two example scripts. One of them just prints out notes, the other is a straight port of the xm2octode converter that yields identical output.

Good: once you have Python installed in your system, you can easily edit, develop, modify the conversion scripts without need of having a compiler around, using popular modern scripting language. Handy for quick prototyping. Also cross-platform.

Bad: it is (edit: was) really slow, a large Octode module takes a few seconds to convert even on a quad core i5.


Sure, that's not a game changer or anything special, but yet another way of doing things.



Note: I'm personally not a big fan of Python, but use it occasionally. So I have no idea how good Python code should look like. I did it so it just works, not pretty.

434

(10 replies, posted in Sinclair)

The issues with Arduino: either a large one needed, like Mega2560 ($10-15), or the normal one with more glue logic (extra latch chip, more wiring). Either way, an SRAM chip is needed, plus some 74xx glue logic. Another issue is that such a thing has to be written in AVR assembly code, to gain enough speed (Arduino normally running at 16 MHz, so just ~4 AVR instructions per Z80 cycle), while the Arduino IDE simply not designed to write assembly code. It is possible, but the syntax is horrible. Can be worked around with a tool that would translate normal AVR assembly to the Arduino syntax, but this kind of defies the main idea behind Arduino's popularity that you can easily read and modify the sketch code.

Emulating Z80 with an MCU, such as AVR, still does provide some benefits. For one, real Z80-based system would also need a boot ROM with communication routines, and communication process would take Z80 time, i.e. communication run on Z80. Emulated system can do communication process fully transparent, keeping engine code very simple and allowing to modify its parameters (any RAM value) at any time from the outside.

I think a good start would be writing a Z80 emulator that would emulate AY files off the internal ROM (to avoid hooking up the external RAM yet). Then it can be developed into actual device. I'll think about it.


On an not very related note, utz, i'm sure that after you managed to do so many amazing engines, and figure out the TI calcs stuff, you'll be able to figure out the Arduino stuff in no time. It is very simple, actually, All you need is to get Uno or something, and connect a speaker. Maybe you'll think up something cool to do with it. I haven't, because the thing is too powerful to just write conventional beeper engines - it has enough oomph to generate high resolution video signal purely in software, not to mention good fidelity multichannel audio.

435

(10 replies, posted in Sinclair)

(we probably should cut this into another thread)

For a beeper card, either way it needs a CPU/MCU, SRAM (32-64K), and some interface to the Spectrum bus. AVR is way more powerful than Z80, but totally different too, so all existing engines and trackers wouldn't be much of use. I personally would like to have a card that allows to put our existing achievements into use.

It is possible to actually emulate Z80 on AVR, but not sure if such approach provides an advantage. There are AVR-based AY-3-8910 emulators around, with AVR being able to also handle interfacing without external components (besides the normal AY decoding logic), so maybe that's the advantage. Basically it should be just two big chips, AVR and SRAM, and a little bit of 74xx logic, with the difficult part being the Z80 emulation code. But it has been done too, with sources. So maybe that's time to actually try to do such thing, as now there are things that can make it possible without putting too much effort.

Going to be a lot of wiring, though, and an AVR with tons of pins is needed. Like, 15+8+8+3+1=35 pins (32K SRAM address, SRAM data, Spectrum data bus, Spectrum bus control signals, output). So smaller Arduinos is out of question, only large ones, such as Mega2560. have enough pins.

Cool stuff, really good sound for such old and limited system.

The demo made me think again how cool it would be to have a dedicated beeper sound card (Z80+RAM) for ZX Spectrum, to allow such music play alongside the gameplay in 48K games.

Edit: Following discussion about beeper sound card split to here: http://randomflux.info/1bit/viewtopic.php?id=119

437

(135 replies, posted in Sinclair)

To make vibrato, we need something that goes back and forth, while saw only goes one direction with jump reset. But it can be used as pointer to a table that turns repeating 0..255 into 0..127..0 in triangle or sine shape, that will give vibrato. But then we don't really need the tricky scheme with using a saw/pulse generator, and can just use a regular counter to go through the vibrato table.

438

(135 replies, posted in Sinclair)

Well, pretty dumb thing like that gives some interesting modulations. Kind of a saw LFO. Unlike Phaser, the addition value for each of generators has different effect.

    ld hl,0
    ld de,60 ; this is like LFO frequency
    ld bc,0
    exx
    
    ld hl,0
    ld de,240 ; this is like base frequency
    ld bc,0
    
loop

    exx
    
    add hl,de
    ld a,h
    
    exx
    
    add hl,de
    ld c,a
    add hl,bc

    ld a,h
    rla
    sbc a,a
    out (#fe),a

    jp loop

439

(135 replies, posted in Sinclair)

I was figuring out how early digital analog synths (i.e. with digital oscillators) were working, I always wondered why they only had saw, pulse, and a combination of those, but not some other waveforms (triangle, sine). I thought that is related to the richer harmonic content the two gives, but it turned out in my experiments that a combined pulse/saw generator can be implemented as simple as add hl,de, so that's likely the reason. It naturally gives the saw waveform in the MSB, and putting a threshold on it (with cp N) gives pulse with duty cycle control. We're using the latter quite often, but maybe there is some use to the saw itself too?

I tried a naive approach, use top 4 bits to generate a short pulse of different width every loop, sounds bad and not usable.

440

(135 replies, posted in Sinclair)

I wonder if there is any useful 1-bit application to this trick: generating saw wave just like we generate different duty cycles. I.e. we have 16-bit accumulator and adder - add hl,de; this automatically creates 0..255 saw waveform in H.

That's like the hypnotoad. Pretty cool.

Well, one application of this fact is extra timbre by doing duty modulation in simple Phaser-like engines:

    ld hl,0
    ld ix,1024
    ld bc,200
    
loop
    add hl,bc
    jr c,$+4
    jr $+4
    xor 16
    add ix,bc
    jr c,$+4
    jr $+4
    xor 16
    out (#fe),a
    
    inc ix  ;that's the duty modulation
    
    jp loop

A note on the Phaser technique. It also allows to control duty, even in its simplest form, as seen in the Phaser1, without the extra CPs. Set both oscillators to the same frequency, but reset phase of both to different values, one to 0, another to 32768 or less (32768 gives 50% duty).

444

(21 replies, posted in Sinclair)

I think it is better to start simple, without adding too many features, and without expecting to expanding the same code to new engines. In general, it needs to be a prototype, to see how convinient the current idea could be. Later we can redo it as an improved, expandable version, having better understanding how to design such thing.

445

(21 replies, posted in Sinclair)

I think that to keep things under control and in a convinient way, we should use ABC or MML as a base, but not fully match to the standard. I.e. add convinient shortcuts for things.

For one, we can implement a 'subroutine', just like in BASIC. The subroutine can be either another sequence of notes, or a set of parameter changes. I.e. there would be no instrument per se, just immediate parameters change that could be abstracted as subroutines. For example, ^ used as a call operator (to keep it short) working by a label:

^ins0 C2 E2 G2 R2 ^ins1 C2 E2 G2 R2

ins0:
duty=50
detune=2
ret

Also, to keep it short, I'd suggest to allow using a note name without duration by remembering the last value, i.e. C2 E2 G2 R2 equal to C2 E G R.


Using calls, we actually won't need to have an order list equivalent. Just write patterns with labels and rets, then the entry point will be just series of calls.

^pattern1 ^pattern2 ^pattern1 ^pattern3
stop

pattern1:

C2 E2 G2 B2

ret

...

One thing to think deeper is how to use chords in an convinient way, not split per a number of data sequences. Maybe some operator like + (C+E+G2 for Cmaj).


I imagine this system as being written in some scripting language, such as Python, with infrastructure a bit similar to the 1tracker's, i.e. the compiler script generates text data (DB/DW), then an assembler (maybe just SjAsm/Pasmo, or also written in the script language) gets invoked to compile that into AY/TAP/SNA/etc.

446

(21 replies, posted in Sinclair)

garvalf, totally agree on the syntax for notes and durations. I don't like MML for the same reasons, that's why I said 'MML-like'. ABC looks much better.

447

(21 replies, posted in Sinclair)

Yes another masterpiece that is also really difficult to support in a tracker.

I thought, maybe instead of the XM conversion or all-for-one tracker we can design and create a MML-like system for these engines? One that compiles a human readable text into a AY/TAP/BIN (so you can hook it up to the Notepad++ and hear the result just by pressing F5 or something). Text allows more flexible input than tracker's row/column based grid, like variable number of channels and parameters.

I highly agree with that point. AY file format is really old and imprecise way to store ripped music, it seriously needs to be obsoleted and replaced with a new properly designed format.

449

(25 replies, posted in Sinclair)

FM2149 is actually called YM2149, not related to FM sound (synthesis) at all.

450

(25 replies, posted in Sinclair)

Maybe I'm a bit behind, but to my recent knowledge there was no OPN implementations out there, not even for Sega Genesis FPGA replicas (they had no sound at all). It is quite unlikely someone managed to create good YM2203 FPGA implementation just for such rare sound expansion, the chip is more complex than the whole ZX Spectrum, and even trickier to get it done near-perfect. So I see two possibilities - the thing is a software emulator, then it can have FM emulation; the thing is FPGA implementation, then it lacks FM.