Music library brainstorm


#1

Hi, I have been brainstorming on music on Arduino a lot (even before Arduboy). We (TEAM a.r.g.) have a library that can play 4 channel tracker music on the Arduino. BUT is needs work, more compression.

There are 2 possible routes for tracker music:

  • we use sample based tracker music (MOD tracker)
  • we use synth based tracker music (AHX tracker)

Now this is the tricky part: does synth based tracker music need more “processing cycles” and less RAM. Samples take more RAM (obviously) but does it need lesser “processing cycles” ?

Next thing to consider: Tracker music uses: instruments, channels, patterns, put together in a song. But classic mod ( amiga module file) don’t use enough compression. And empty channel will take as much space as a full one. (it will be an array of zeros).

So our tracker player should have:

song = how much channels are used, speed, repeat song at position x (0 = no repeat), order of patterns to be played
pattern = length of the pattern?,instrument, note, volume?, effect, (every channel has it’s own pattern. In a classic mod, all channels are included in the same pattern)
instrument = or the sample, or the synth instrument (with an optional indication of which portion of the sample can be repeated to hold a sustained note)

We need people who can actually help creating such a player.

Song ={3, 64, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4}

patterns channel1 = { pattern1ch1, pattern2ch1, pattern3ch1, pattern4ch1}
patterns channel2 = { pattern1ch2, pattern2ch2, pattern3ch2, pattern4ch2}
patterns channel3 = { pattern1ch3, pattern2ch3, pattern3ch3, pattern4ch3}

pattern1ch1[64] = {5448, 5801, 3d16, 0024, 0000, 404b, 1400, 0c00, …}
pattern1ch2[0] = {}
pattern1ch2[4] = {2c00, 3600, 2600, 3500, …}

instrument 1 = …
instrument 2 = …
instrument 3 = …
instrument 4 = …
instrument 5 = …

PS: we should definitely look into AHX tracker music (chiptunes) AHX is a protracker-like music editor that was designed especially to create C64-like synthetic tunes. There is no support for sampled instruments as chip tunes are made to be as small in size as possible. So an average AHX tune has a length of about 200 bytes - 5 kbytes (unpacked). All waveforms of the C64 are supported: Triangle, Sawtooth, Square and White Noise. Also Hi-/Lo-Pass filtering effects are supported (ring-modulation is hopefully to come in AHXv3).



[WIP] 4 channel Music
Arduboy Kickstarter version design discussion
How do I lower the volume?
#2

Here we can download the official C++ source code for AHX and other links: http://www.exotica.org.uk/wiki/Abyss’_Highest_eXperience


#3

I can’t help with programming, unfortunately, but this is something that can/will be very useful for games. A nice lightweight music playback engine will hopefully go a long way to making good game experiences.

I’m really hoping to use my Arduboy to learn a lot about programming, and music is something I’ve been pondering lately.


#4

I started a Github repository: ATMlib Arduboy Tracker Music library


#5

And the best part: We got stg convinced (the creator squawk: the Arduino tracker player) to help us out !


#6

For the record, we wrote some code with @stg on IRC to prove the following things:

  1. We can use pins 6/12 to make a louder sound (their PWM outputs are symmetric)
  2. Only Timer 4 is required for mixing two sounds in software
  3. Using only one timer for both interrupts & PWM doesn’t more cycles than using two timers

Here is the code:

// Generate a super sawtooth on pins 6+12 using ISR+PWM on Timer 4

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

void setup() {
  // Enable buzzer output pins
  pinMode(6, OUTPUT);
  pinMode(12, OUTPUT);

  // Set Timer 4 divider to 1 (0001)
  cbi(TCCR4B, CS43);
  cbi(TCCR4B, CS42);
  cbi(TCCR4B, CS41);
  sbi(TCCR4B, CS40);

  // Set COM4D1..0 to enable both OC4D and _OC4D (01)
  cbi(TCCR4C, COM4D1);
  sbi(TCCR4C, COM4D0);
  
  // Set counter mode for fast PWM (00)
  cbi(TCCR4D, WGM41);
  cbi(TCCR4D, WGM40);

  // Enable timer 4 overflow interrupt
  sbi(TIMSK4, TOIE4);
  
  // Clear interrupt just to be sure
  sbi(TIFR4, TOV4);
}

void loop() {
}


// Timer 4 overflow interrupt
byte divider;
word f1, f2, f3, f4;
ISR(TIMER4_OVF_vect) {
  // Sample rate 1:3 prescaler
  if(++divider < 3) return;
  divider = 0;

  // Quick non fractional sawtooths
  f1 += 3328;
  f2 += 3338;
  f3 += 3348;
  f4 += 3358;
  
  // Mix and output using PWM
  OCR4D = (byte)(f1 >> 10)
        + (byte)(f2 >> 10)
        + (byte)(f3 >> 10)
        + (byte)(f4 >> 10);
}

(Chris Smith) #7

More detail over at this post, but 5/12 lets you keep this option by setting up comparators D and A to generate opposite signals, while opening up the option to use comparators D and A – still on one timer – for complete different generation of signals, which are hardware mixed by the signals.


#8

So I did further testing with pin 5 (_OC4A) and pin 12 (_OC4D) and for me it seems to sound good enough, even out of phase.

The problem is, can we disable OC4D and still have _OC4D on?

Here is the code:

// Generate a super sawtooth on pins 5+12 using ISR+PWM on Timer 4A+4D

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

void setup() {
  // Enable buzzer output pins
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(12, OUTPUT);

  // Set Timer 4 divider to 1 (0001)
  cbi(TCCR4B, CS43);
  cbi(TCCR4B, CS42);
  cbi(TCCR4B, CS41);
  sbi(TCCR4B, CS40);

  // Set COM4D1..0 to enable both OC4D and _OC4D (01)
  cbi(TCCR4C, COM4D1);
  sbi(TCCR4C, COM4D0);

  // Set COM4D1..0 to enable both OC4A and _OC4A (01)
  cbi(TCCR4A, COM4A1);
  sbi(TCCR4A, COM4A0);

  // Set counter mode for fast PWM (00)
  // EDIT: "fast" should be 8 MHz instead of 62.5 kHz, so maybe this is wrong
  cbi(TCCR4D, WGM41);
  cbi(TCCR4D, WGM40);

  // Enable timer 4 overflow interrupt
  sbi(TIMSK4, TOIE4);

  // Clear interrupt just to be sure
  sbi(TIFR4, TOV4);
}

void loop() {
}

// Timer 4 overflow interrupt
byte divider;
word f1, f2, f3, f4;
ISR(TIMER4_OVF_vect) {
  // Sample rate 1:3 prescaler
  if (++divider < 3) return;
  divider = 0;

  // Quick non fractional sawtooths
  f1 += 3328;
  f2 += 3338;
  f3 += 3348;
  f4 += 3358;

  byte freq = (byte)(f1 >> 10)
              + (byte)(f2 >> 10)
              + (byte)(f3 >> 10)
              + (byte)(f4 >> 10);

  // Mix and output using PWM
  OCR4A = freq;
  OCR4D = 255 - freq;
}

(Scott) #9

If this turns out to be a problem, we could put the display RST signal on Pin 6, which uses OC4D. (Display CS could go on Pin 13.) Pin 6 would be programmed as an input and an external pull-up would be added if necessary (if the internal pin pull-up turns out to be overridden by PWM).

To reset the display, you would disable OC4D PWM mode and change Pin 6 to output low. After resetting, Pin 6 would be changed back to an input and the pull-up would pull the reset line high.

But maybe now we have the same problem with Pin 13 (OC4A) being the compliment of Pin 5 (_OC4A)?


(Scott) #10

The datasheet is quite complicated, but it looks to me like you can disable a complimentary output while the normal output is enabled but not vice versa. If this is true, then perhaps the the best speaker pins to use would be 6 (OC4D) and 13 (OC4A). Display RST and CS could use pins 5 and 12.

This would mean the loss of the ability to alternatively use Timer 3 (on pin 5). I don’t know if that’s very important or useful.


#11

What about pins 5/13?

  • To increase the sound, you would enable OC4A and _OC4A
  • To mix two sounds in hardware, you would enable OC3A and OC4A

For the record:

  • 3: OC0B
  • 5: OC3A, _OC4A
  • 6: OC4D
  • 9: OC1A, _OC4B
  • 10: OC1B, OC4B
  • 11: OC1C, OC0A
  • 12: _OC4D
  • 13: OC4A

(Scott) #12

With regards to my previous suggestion to use pins 6 and 13, can’t you generate two different sounds using OC4D and OC4A?


#13

Yes, but you do not get the option to have a complementary output for free. (I agree that 5/13 or 6/13 is more or less equal.)


(Scott) #14

It’s a trade off. With 6/13 you need to do a small amount of extra work to set up complimentary (increased volume) output but you don’t have the hassle of handling normal use of pins 5 and/or 12.

I guess we need to determine for sure if it’s possible to have a complimentary PWM output on a pin while still being able to use the pin dedicated to the normal phase as a general purpose output. I used section 15.12.3 TCCR4C – Timer/Counter4 Control Register C in the ATmega32U4 datasheet as a reference.


#15

No, to my reading of the datasheet and to @stg’s reading, that is not possible. That is the problem of pin 12.

Regarding pins 5/13 and 6/13, I do not know if it is better to use OC3A/OC4A or OC4D/OC4A to mix two sounds. I think @ChrisS had some opinion on this.


(Scott) #16

Well, I’d say we need to decide which is better quite quickly, since it looks like if we try to do PWM on the current speaker pins 5 and/or 12, it will cause problems with display DC (Pin 13) and RST (Pin 6).

Hopefully we can decide in time so that @bateske can make changes before the hardware design is locked down.


(Chris Smith) #17

If you are mixing two independent sounds, the lock-in or phasing of the underlying timers should not matter. This is entirely in the audio domain.

If you are trying for volume amplification, with matching but complementary outputs, then you need the phase matching. With various harmonics are differing frequencies, and therefore at differing phase relationships, you might only get volume amplification on some frequencies, while getting cancellation on some of the harmonics. It might actually sound quite cool - I know people who play with exactly those features - but they do that precisely because it is unpredictable. “Unpredictable” is usually not a desirable feature when you’re aiming for amplification.

As to the ability to keep the complementary outputs without the original - let’s just say, that is one really complex section of datasheet!

First …

10.3.3 Alternate Functions of Port D, • T1/OC.4D/ADC9 – Port D, Bit 6

OC.4D: Timer 4 Output Compare D. This pin can be used to generate a high-speed PWM signal from Timer 4 module, complementary to OC.4D (PD7) signal. The pin has to be configured as an output (DDD6 set “one”) to serve this function.

I don’t think there is any doubt on this point - if the complementary signal is generated, we can route it out this pin.

On to 15.12.3 TCCR4C – Timer/Counter4 Control Register C, particularly • Bits 3,2 - COM4D1, COM4D0: Comparator D Output Mode, Bits 1 and 0.

The sentence that would give me doubt is “The complementary OC4D output is connected only in PWM modes when the COM4D1:0 bits are set to “01”.

So - when do we set 01? My reading of tables 15-15, 15-16, 15-17 is that in Normal (Non-PWM) mode (Table 15-15) we do indeed lose the complementary output. (This would seem to contradict the sentence above.) But in Table 15-16. Compare Output Mode, Fast PWM Mode and Table 15-17. Compare Output Mode, Phase and Frequency Correct PWM Mode, I note that when setting 01 (which has different meanings in the two tables), the complementary output is listed as connected, but disconnected in all of the other three modes.

Finally, the block diagram for the timer/PWM section strongly suggests that the ‘connected’ and ‘disconnected’ applies inside the block. This would seem to indicate that, as long as the signal is ‘connected’ in the PWM block, it is available for the alternate function of the pin.

So - it’s back to the libraries. Is the 01 mode in either Fast PWM Mode or the Phase and Frequency Correct PWM Mode the setting that is used in the libraries? I would have to hand that question back to someone extremely familiar with those libraries. If the libraries use different modes, then we lose the complementary output. If they do use the 01 mode, then it appears we keep the complementary output.


I’ve tried to be almost excessively complete, so that if I am wrong, it’s far easier for someone to point out where my logic chain broke.


(Kevin) #18

Hey guys I found this project that is only using one pin to generate 4 voices 5 notes polyphony:


http://wiki.openmusiclabs.com/wiki/MixtapeAlpha

Soooo… pins 6 and 13? is where we are at now? Awesome work guys I can barely keep along with your knowledge of timers!


#19

to be clear: we can do 4 voices with one pin. The suggestion of 2 pins came, when there was the idea of amplification or effects. And actually it came independent of the 4 voices tracker idea. All existing libraries that do 4 voice music use 1 pin.

2 pins give more options, if chosen correctly.


Arduboy Kickstarter version design discussion
(Scott) #20

But in all “01” modes where _OC4D is given as connected, the non-inverted OC4D (Port D Bit 7, Arduino Pin 6) is also shown as connected. So as far as I read it, if Pin 6 is set as an output it will always be the compliment of _OCD4 on Pin 12.

This means if we want to use Pin 6 for display RST, we will have to use the trick I mentioned of setting it to an input until we want to do a reset (and for this we may also need an external pull-up). The same goes for OC4A on Pin 13 if we try to use _OC4A on Pin 5. I don’t think it would be wise to try to use this “idle as an input” trick with display CS or DC.

Whether we need an external pull-up depends on if the internal pull-up becomes disabled every time the OC4x signal goes low. This would be pretty easy to test.

Assuming we can’t prevent an output pin on OC4x from toggling when complimentary _OC4x toggles in mode “01”, then I think it’s best to use one of the following:

  • Speaker on pins 6/13:
    • Uses OC4D and OC4A for separate sounds.
    • For increased volume OC4D and OC4A are programmed individually to generate synchronized complimentary signals.
    • No problems with using Pin 5 (_OC4A) and Pin 12 (_OC4D) as general purpose outputs for the display.
  • Speaker on pins 5/13:
    • Uses OC3A and OC4A for separate sounds.
    • For increased volume OC4A and _OC4A can be easily used.
    • No problems with using Pin 6 (OC4D) and Pin 12 (_OC4D) as general purpose outputs for the display.

So it’s back to the question: Is it better to use OC3A or OC4D to generate PWM sounds separate from PWM sounds generated with OC4A? If it’s a toss up, we should consider if leaving Timer 3 unused might make it more useful for other non sound related purposes.