1 (edited by Shiru 2017-02-26 10:37:06)

Topic: Tritone on Arduino

Now we're getting into something tricky and less straightforward that is not transferring to the Arduino and C well. That's the Tritone engine.

The main feature of the engine is the uneven volume balance, giving virtual volume control without the usual PWM'ish artifacts. It was achieved by using the standard interleaving technique, but assigning time slots of different length for each of the channels. I.e. longest slot gives loudest sound, shortest slot gives quitest sound. That's easy to achieve with the timed code, but in C on Arduino we don't have such luxury.

It (probably) can't be done with just a single fixed-frequency timer interrupt at a sample rate as high as half of the shortest slot (to be able to align output sequence to 2,3,4 samples to keep the original volume balance) - that would give over 200 KHz, and that's likely too much, just the interrupt call/return will hog most of CPU time.

A better approach that I come up with is to set a train of interrupt handlers, each sets up the timer to another value. The hardware interrupt handler function only contains call of the actual handler by a function pointer. There is three actual interrupt handling functions that gets called with the timer interrupt in a circular sequence. Each one sets up the next timer value, outputs current slot, calculates whathever is needed, and sets up the pointer to the next handler function. This gives steady sample rate just like the original Tritone had (actually 22988 Hz, due to the timer prescaler, which is not far from the original 22875 Hz), with proper interleaving slots aligment.

Drums were another tricky part. As the original code sure does not translate to the timer interrupt-driven approach well, I decided to do it lazy way this time - as 1-bit samples of the original sounds. There is fourth interrupt handling function that sets up the timer to doubled sample rate (45977 Hz) and plays sample while keeping the sync with parser thread. Once sample is done playing, the handler reverts to the tone synth handlers loop. Samples of the drums were captured from Unreal Speccy at 96 KHz, downsampled to the desired rate, amplified a lot to get hard clipping, so they're mostly max or zero volume, converted to 8-bit, then processed with a custom tool written in Python. To save Flash memory I used a RLE-like packing that only records how many samples there were between changes 0 to 1 or vice versa (I think that's what Speak Easy uses on ZX Spectrum), that's what the tool doing, besides of finding individual drum shots in a single WAV file.

Yet again, you can compile Tritone songs for the Arduino port with Beepola, exporting them as BIN without player. The test song is by garvalf - 'Bourrasques'.

Post's attachments

arduino_tritone.zip 29.56 kb, 29 downloads since 2017-02-26 

You don't have the permssions to download the attachments of this post.
website - 1bit music - other music - youtube - bandcamp - patreon - twitter (latest news there)

2 (edited by garvalf 2017-02-18 10:09:07)

Re: Tritone on Arduino

it sounds very good too (I've only tested through an old speaker), very close to original it seems. What you did for getting the conversion is very impressive!

Yet, I've found a few problems:

- I have a song with a very fast tempo of 19 (in the zip it's called music_data_bourrasque.h and the original from zx version can be heard there http://chipmusic.org/garvalf/music/bourrasques ). The arduino code doesn't handle that probably because it's too fast. The song is slowed down, with it seems some accelerations and decelerations (I guess it's not possible to fix this, which I can understand)

- on at least two of my songs, (music_data_souvenirs_tritone.h and music_data_snakecharmer.h) the arduino stops the music before the end, on the first one it's after just a few notes, on the second one it's a bit longer but not much. Probably there are some data which are confusing the engine...

3 (edited by Shiru 2017-02-18 13:28:13)

Re: Tritone on Arduino

Thanks for report, but you forgot to attach the files.

Regarding the slower tempo, no, Arduino can handle crazy speeds, that's not the issue. That's seem to be just a misimplentation of the thread sync (row counters in beeper engines may work in tricky ways). Thinking how to fix it now.

Edit: made the tempo fix and replaced the test song with yours. The file attach replaced, please re-download.

website - 1bit music - other music - youtube - bandcamp - patreon - twitter (latest news there)

4 (edited by garvalf 2017-02-18 16:40:02)

Re: Tritone on Arduino

ok, I'll have a look at the new version this evening, thank you.

I don't know what happened with the attached files. Here it is again. (edit: I know, I've just selected the file with the browser, but forgot to click on "add file")

(edit) I've quickly tested it, tempo is ok for the "bourrasque" tune, but it still stops during the replay. For the other "souvenirs", it doesn't stop at the beginning like before, but I haven't replayed it completely. I'll test more soon. Thanks again for the fix anyway.

Post's attachments

garvalf_tritone.zip 71 kb, 4 downloads since 2017-02-18 

You don't have the permssions to download the attachments of this post.

5 (edited by garvalf 2017-02-19 09:19:03)

Re: Tritone on Arduino

the one called music_data_snakecharmer.h replays completely. "Incantation" stops after a while, like "bourrasque". The one called "souvenirs"  plays for most of the song, but is cut anyway before the end. The mister beep song replays correctly. What could it be? What is strange is one parts plays well, while later the same part which is repeated will stop abruptly.

I'll test it on another arduino, in case it's the one I use (official Arduino Uno) which has some limitations (but it doesn't stop like that with other engines). I've already encountered differences between arduino versions, for example a shield was working on a clone, but not on the official one, and not either on another clone.

Re: Tritone on Arduino

That's weird, but I can't really test it - neither I know how the songs supposed to sound, nor my Arduino works stable while it connected to the PC, it randomly resets after a few seconds, and I haven't found a fix for this yet. If you will be able to provide a test where it will be easy to spot the issue, like really hanging on the first notes or something, I'll be able to examine and fix the problem (none of your tests stopped too soon for me, but I wasn't able to listen them entirely).

website - 1bit music - other music - youtube - bandcamp - patreon - twitter (latest news there)

7 (edited by garvalf 2017-02-19 12:17:24)

Re: Tritone on Arduino

I remember before your last fix one song stopped after only a few seconds. But after your fixed it worked (at least longer).

I've tested on another arduino (a dccduino clone), and it's the same, it stops at the same moment. But this moment seems rather random from one song to an other: I've isolated the part when I stopped, and removed all patterns before it. Then this time it won't stop there, but only after a while.

In the original "bourrasques" tune it stopped after 25 seconds, now with this abridged version, it's after 12 seconds, will it be enough for you to test? Here is the music_data as attachment.
I've put 2 files, one (v0) with the original data, only the part before the bug were removed, but the song last until the normal end. For the other one, I've removed the end, I've only kept the data until it stops + about 3 seconds, so it's supposed to stop after 15 seconds but will stop at 12 sec.
I've also tried to remove the loop (I chose exit instead before compilation) but it's the same, stop after 12 sec (the output data are identical)

Will your arduino reset as well if you plug it to a 5V phone charger, after you've uploaded the sketch?

Post's attachments

music_data_bug.zip 4.4 kb, 3 downloads since 2017-02-19 

You don't have the permssions to download the attachments of this post.

Re: Tritone on Arduino

Thanks for the information, I'll try to figure out what's going wrong. It has to be something with the code. Maybe the interrupt train system is not very reliable in some case. Although it is very determined and set to exact same state at start, so it expected to fail always at the same spot.

My Arduino works well with the external 5V power source plugged into USB. The problem is that I need to have it hooked up to PC in order to debug. Haven't found a suitable 9V adapter just yet ('minus outside' are rarity here, don't have spare ones to rewire either), hopefully it will fix the issue.

website - 1bit music - other music - youtube - bandcamp - patreon - twitter (latest news there)

9 (edited by Shiru 2017-02-24 09:42:24)

Re: Tritone on Arduino

Found the power supply. Didn't help, the Arduino keeps resetting after a few seconds while it connected to the PC. So that's not a power issue, likely a software one (something accesses the USB port, causing resets).

website - 1bit music - other music - youtube - bandcamp - patreon - twitter (latest news there)

10

Re: Tritone on Arduino

Was just reading about this guy's troubles, so a software issue seems quite possible.

Perhaps it's easier to disable auto-reset until you've found the culprit?

Re: Tritone on Arduino

I also found and read that article about phone software interferring with Arduino. Actually tried to remove all drivers that use COM mode, figured out which ones they are using USBDeview. Nothing changed at all - unstable upload, constant resetting. Guess the only option that is left is to disable the auto reset indeed, will try it later.

website - 1bit music - other music - youtube - bandcamp - patreon - twitter (latest news there)

12 (edited by garvalf 2017-02-25 07:40:02)

Re: Tritone on Arduino

Could the use of a linux double boot be an option for you?
Or if the problem is the arduino software, trying an older version?

13 (edited by Shiru 2017-02-26 10:43:17)

Re: Tritone on Arduino

No, Linux double boot is not an option. Unfortunately my notebook that is normally used for such things broke, and I don't want to mess with my main working PC having no backup working machine.

Interestingly, I noticed that while the COM monitor is active, the resetting stops, so it became a bit easier to debug.


Anyways. I think I found a fix for the player hanging. That's likely related to the way I was invoking the drums - by changing the interrupt handler pointer from the main thread. But that's AVR8, so this operation is not atom - it changes two bytes one by another, and there is a good chance (considering the high sample rate) that timer interrupt will occur in the middle of the change, calling the half-changed address of the handler function. I tried to fix it by changing the handler only in the timer interrupt handler, where it is guaranteed that another interrupt won't occur. Re-attached the code to the first post, please test.

website - 1bit music - other music - youtube - bandcamp - patreon - twitter (latest news there)

14 (edited by chupo_cro 2017-08-03 01:22:13)

Re: Tritone on Arduino

Since parser_sync_enable is just one byte, this time you can fix the sync code just by:

//set up parser sync counters

parser_sync_l=song_tempo&255;
parser_sync_h=song_tempo>>8;
parser_sync_enable=1;

//wait for the next row
//delay 1 is important, by some reason song tempo starts to jump a lot without it

//while(parser_sync_enable) delay(1);
while(parser_sync_enable)
    ;
Chupo_cro

Re: Tritone on Arduino

@chupo_cro: I've tried your code, and it seems to fix the problem, thanks.

I don't remember how was Shiru's code before he fixed the tempo problem, but now we have both the correct tempo, and the songs won't halt after a while (the "bourrasque" tune lasts 2 minutes, until the end, after it loops, instead of 10 seconds before)

Like for the other engines, we can add a nice led for lighning during the beats:

if(drum_pulse_length)
    {
        --drum_pulse_length;
               digitalWrite(LED,HIGH);
    }

16 (edited by chupo_cro 2017-08-04 15:06:07)

Re: Tritone on Arduino

garvalf wrote:

@chupo_cro: I've tried your code, and it seems to fix the problem, thanks.

I don't remember how was Shiru's code before he fixed the tempo problem, but now we have both the correct tempo, and the songs won't halt after a while (the "bourrasque" tune lasts 2 minutes, until the end, after it loops, instead of 10 seconds before)

Like for the other engines, we can add a nice led for lighning during the beats:

if(drum_pulse_length)
    {
        --drum_pulse_length;
               digitalWrite(LED,HIGH);
    }

Hi, I am not aware how the code looked like before, I have downloaded it this night. Since the variable used in a while loop is 8 bit, the only change was to remove the delay. That is possible because parser_sync_enable is always zero when loading parser_sync_l and parser_sync_h. If that would not be the case cli() and sei() would have to be added before parser_sync_l and after parser_sync_h respectively to prevent an interrupt in between loading low and high byte.

As for the 'light show', I have this little nice ATmega128 board (a picture) which has two SMD LEDs connected to the lowest two bits of the PORTA so the nice effect can be done by:

if(n>=2&&n<128)  //drum sound
    {
        drum_data=(const unsigned char*)pgm_read_word(&(drum_sample_list[n-2]));

        drum_output=0;
        drum_pulse_length=0;
        drum_sync=0;
        drum_request=1;

        PORTA ^= 0x03;        // turn off one of the LEDs and turn on the other one
                
        ++pattern_ptr;
    }

For that to work I added:

// LEDs
DDRA = 0x03;        // PA0 & PA1 = output
PORTA = 0x01;        // light on one of the LEDs

The same code could be used with Arduino by changing the port (and maybe the pins) and connecting one more LED.

The positions to add 'light show' code are:

Phaser1:

else  //118..127 is a drum
{
    drum_ptr=0;
    PORTA ^= 0x03;        // turn off one of the LEDs and turn on the other one
    drum_sample=1<<(tag-118); //bit mask for the drum sample

    cli();
    parser_sync=MUSIC_FRAME*1;  //drum always take one tempo unit, so it normally followed by the wait command
    sei();
}

Octode:

if(n>=0xf0)
{
    click_drum=7-(n-0xf0);
    click_drum_len=128;
    PORTA ^= 0x03;        // turn off one of the LEDs and turn on the other one

    ++pattern;  //skip to the next byte
}

Qchan:

if(tag>=0x81) //a drum
{
    tag=(tag-128)<<1;
     
    click_drum_cnt_1=tag;
    click_drum_cnt_2=tag;
      
    click_drum_len=0;
    PORTA ^= 0x03;        // turn off one of the LEDs and turn on the other one
 
    ++pattern_ptr; 
}

Fuzzclick:

if(tag>=0x81&&tag<0x81+4)
{
    drum_data=(const unsigned char*)pgm_read_word(&(drum_sample_list[tag-0x81]));
                
    drum_output=0;
    drum_pulse_length=0;
    drum_request=1;       //interrupt handler will get changed in the next tone interrupt

    PORTA ^= 0x03;        // turn off one of the LEDs and turn on the other one
}

It is quite nice to watch the dot jumping on every drum beat, I might record a video.

I am BTW using this ATmega128 board only when developing because the programs can fit in the memory even with the disabled compiler optimizations which can sometimes be neccesarry and because I can use cheap JTAG. The PCBs which I like to use for final products are this one and this one. These are Dropbox links to picture albums, no need to register.

Post's attachments

ATmega128.jpg 41.75 kb, file has never been downloaded. 

You don't have the permssions to download the attachments of this post.
Chupo_cro