DIYstompboxes.com

DIY Stompboxes => Digital & DSP => Topic started by: Ksander on January 04, 2023, 02:52:16 AM

Title: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 04, 2023, 02:52:16 AM
Dear all,

On technoblogy.com, there is a pitch shifter project which I built as a pedal. I adapted the project such that the increments are in semitones. It works well, but the sound quality is not great. As an improvement, I was thinking whether using the full 10-bit ADC reading instead of just the most significant 8-bits, and then simulating 10-bit PWM by dividing the ADC reading in 4 8-bit chunks could work. I've implemented it in the code (below), and found that the principle seems to work, but the pitch shifting does not; with the adapted code, I can lower the pitch, but not increase it. I don't know much about programming, and would greatly appreciate some input on whether there is something wrong with my code, or whether this is some limitation of the attiny.

This is the code:


/* Audio Pitch Shifter

   Based on Pitch Shifter and 10-bit PWM projects by
   David Johnson-Davies - www.technoblogy.com -
   ATtiny85 @ 8MHz (internal oscillator; BOD disabled)
   
   CC BY 4.0
   Licensed under a Creative Commons Attribution 4.0 International license:
   http://creativecommons.org/licenses/by/4.0/

   Everything is done by interrupts

   20220706 - KW - included button debouncing
   20221031 - KW - implemented shifting by semitones
   20230101 - KW - revised code to get 10-bit sampling and PWM, instead of 8-bit
*/

volatile uint16_t Buffer[128];                // Circular buffer
volatile uint8_t ReadPtr, WritePtr, LastPtr, New;
volatile uint16_t ButtonDelayCounter = 0;    // KW 20220706
volatile signed char N = 0;                  // KW 20221031
volatile int Cycle = 0; // KW 20230102 (from 10 bit PWM example at www.technoblogy.com)

// save ADC value in buffer
ISR (ADC_vect) {
  Buffer[WritePtr] = ADC + 512; // KW: add 512 such that the ADC value becomes unsigned
  WritePtr = (WritePtr + 1) & 0x7F; // KW: '& 0x7F' - wrap at 128 values
}

// Timer interrupt - read from buffer and output to DAC
ISR (TIMER0_COMPA_vect) {

  static int remain; // KW: divide the 10-bit ADC reading over four chunks
  if (Cycle == 0) remain = Buffer[ReadPtr];
  if (remain >= 256) { OCR1A = 255; remain = remain - 256; }
  else { OCR1A = remain; remain = 0; }
  Cycle = (Cycle + 1) & 0x03;

  if (Cycle == 3) { // KW 20230102: increment reading pointer when the four chunks have been generated
    ReadPtr = (ReadPtr + 1) & 0x7F;
    if (ButtonDelayCounter>0) ButtonDelayCounter--;                  // KW 20220706: for debouncing button presses
  }
 
}

// Pin change interrupt adjusts shift
ISR (PCINT0_vect) {
  int Buttons = PINB;
  if (ButtonDelayCounter == 0) {          // KW 20220706
    if ((Buttons & 0x01) == 0) {
      N++;                                // KW 20221031
    }
    else if ((Buttons & 0x04) == 0) {
      N--;                                // KW 20221031
    }
    OCR0A = round( 112 / pow(1.0595,N) ) - 1; // KW 20221031, KW 20230102: in- or decrement shifts in semitones
    ButtonDelayCounter = 500;             // KW 20220706
  }                                       // KW 20220706
}

// Setup **********************************************

void setup () {
  // Enable 64 MHz PLL and use as source for Timer1
  PLLCSR = 1<<PCKE | 1<<PLLE;     

  // Set up Timer/Counter1 for PWM output
  TIMSK = 0;                              // Timer interrupts OFF
  TCCR1 = 1<<PWM1A | 2<<COM1A0 | 1<<CS10; // PWM OCR1A, clear on match, 1:1 prescale
  OCR1A = 128;
  pinMode(1, OUTPUT);                     // Enable OC1A PWM output pin

  // Set up Timer/Counter0 to generate interrupt at four times the ADC rate.
  // The ADC provides 10-bit resolution, whereas the PWM provides 8-bit resolution
  // by running the PWM at four times the speed of the ADC, we can simulate 10-bit PWM
  TCCR0A = 2<<WGM00;                      // CTC mode
  // TCCR0B = 2<<CS00;                       // /8 prescaler
  // OCR0A = 55;                             // 17.9kHz interrupt (1M/56)
  TCCR0B = 1<<CS00;                       // KW: /1 prescaler
  OCR0A = 112;                            // KW: 71428.57143Hz interrupt (8M/112), four times the original speed to get 4*8-bit = 10-bit PWM
  TIMSK = TIMSK | 1<<OCIE0A;              // Enable interrupt
 
  // Set up ADC: given that it takes 14 cycles to get an ADC reading, audio is sampled at:
  // 250kHz/14cycles = 17857.14285Hz
  ADMUX = 2<<REFS0 | 1<<ADLAR | 7<<MUX0;  // Internal 1.1V ref, ADC2 vs ADC3, x20
  // Enable | auto trigger | interrupt | 250kHz ADC clock:
  ADCSRA = 1<<ADEN | 1<<ADSC | 1<<ADATE | 1<<ADIE | 5<<ADPS0; 
  ADCSRB = 1<<7 | 0<<ADTS0;               // Bipolar; no need for external circuitry, free-running

  // Set up buttons on PB0 and PB2
  pinMode(0, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  PCMSK = 1<<PINB0 | 1<<PINB2;            // Pin change interrupts on PB0 and PB2
  GIMSK = GIMSK | 1<<PCIE;                // Enable pin change interrupt

}

void loop () {
}
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Kevin Mitchell on January 04, 2023, 10:53:53 AM
I'd have to take time to break this down to fully understand it, so these are only my first impressions.

That's pretty cool, but there are many pitfalls from what I understand. It would make a decent variable pitch shifting fuzz but not much anything else due to lack of resolution. However, you can set the internal clock closer to 16Mhz which would improve the output resolution - maybe smooth it out with an LPF as well. But of course other chucks of the code would have to be altered as well.

Why not declare readPTR as 16-bits to begin with? You can easily bit-shift it to get two separate 8-bit values later on when needed.
I'd love to get into this one myself. But there's a mountain of "gotta finish" projects in front of me  :o
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 04, 2023, 11:06:52 AM
Thanks for the response - clearly, it takes time to see what is going on in the code; I still don't understand completely myself  ;)

The reason the pointer is 8-bits is that it doesn't need to be larger, as the buffer contains only 128 values. The read and write pointers indicate which value from the buffer to write, and which to output to the pwm.When the rates at which they increment are different, you get a pitch shift.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: anotherjim on January 04, 2023, 03:06:03 PM
The resolution is not that bad IMHO, I have a flanger built in a Tiny84 that does ok and the output is entirely from the code. It has 10-bit output in 2 PWM outputs but there are only the lowest 2 bits in one mixed 1/4 amplitude with the 8-bit other. I didn't dare run it with interrupts so it's assembly coded with no stack leaving all the RAM free for the buffer. Most of the important code functions while the ADC is thinking.
The sample rate is 31.25kHz. The Tiny84 doesn't have the pll speed up for the peripherals clock as the Tiny85 and runs on the internal 8Mhz clock. The 85 can double its clock to 16Mhz with the pll, but IIRC that stops the faster counter clock? A faster CPU may be more beneficial for fitting in the code.

Anyway, I've no idea how the algorithm employed here works for pitch shifting. I've thunk about it from time to time but that's as far as I've got. If I did try it I would pick a Tiny85.

I don't really do C code so although you have documented the code well, I haven't understood it well enough to offer any help!

Maybe get more views in the Digital forum?

Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 04, 2023, 03:40:28 PM
I already doubted where to put the topic, I might ask a moderator to move it...

My understanding of how the algorithm works is as follows:
Using one timer, the adc outputs samples at a rate of 17.9kHz, or thereabouts, and these are written to a circular buffer.
You then read from the buffer, and set the duty cycle of the PWM in accordance with the value read. When the writing and reading speed are the same, the output is like a bit crushed version of the input  If you read at half the frequency, you pitch down an octave; if you read at twice the writing frequency, you pitch up an octave. The other timer is used to set this speed.

By dividing the steps in between in cents, you get semitone increases. Which is my contribution  :icon_cool:
And soon hopefully also a functional 10-bit version...
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 05, 2023, 02:48:31 AM
Thanks for moving the topic!
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: niektb on January 05, 2023, 05:08:05 AM
Are you using an arduino-like bootloader? I see the 'common' void setup and void loop structure but I don't see the accompanying includes :) cause if you do we can simplify it a lot I think!

But okay I'm trying to understand the code, am I correct when I think you want to run the PWM at 4 times the ADC speed but not exactly as you want to vary the PWM speed to get the pitch shifted effect? (Cause I see you're using Timer0) I think the consequence might be that you will not actually have 10-bit but sometimes a bit less and sometimes a bit more (but I don't think that's a problem per se)

I have done a tiny bit of C programming for ATTiny's but I'm not that good, do you have a link to the explanation of your original pitch shifter? (for understanding the register settings etc) I already found the one for the 10-bit output:
http://www.technoblogy.com/show?1NGL
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: anotherjim on January 05, 2023, 05:32:54 AM
I think it's always tricky to adapt code, you have to fit your mindset in with the original coders and adhere to the overall concept.

There is a quick and dirty trick to smoothing the output for this kind of thing and that is to maintain "new" output samples at the sample rate by creating average sample values. Always send the most recently read sample from the buffer to the PWM after averaging it with the previously sent sample. The resulting wave will move progressively between the "real" values from the buffer instead of repeating the same and creating a stepped waveform. It's artificial, but I think it sounds better for it.
Averaging is fairly fast for the MCU to do. The code just needs to perform a 16bit addition and divide by 2 by shifting the result right one place.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 05, 2023, 05:39:01 AM
Hi Niektb - thanks for taking an interest in the topic!

The link to the original pitch shifter article is here:

http://www.technoblogy.com/show?1L02 (http://www.technoblogy.com/show?1L02)

The original article is very nice, and allowed me to build a pedal. Still, I found that some useful details were lacking. Things I was able to figure out later are included as comments in the code.

The code that I copy/pasted above is exactly as I have it: there are no additional includes not shown. To actually program the chip I use the Arduino IDE with Spence Conde's attinyCore, and I use an Arduino Uno as ISP. I picked up this "how-to" mostly from technoblogy and some googling.

Your understanding of what I'm trying to do is correct. As a starting point, I run the PWM part at four times the ADC speed, such that the 10-bit ADC reading can be divided over four chunks which, presented sequentially, then have the same speed as the ADC. This is in principle the same trick as described in the other technoblogy post, although I used the compare match interrupt rather than the overflow interrupt. The reason for this is that changing the register value sets the frequency of the interrupt, and thereby allows to vary the write speed relative to the read speed, creating the pitch shifts. The code works partly; the chip gives audio output, and it also produces pitch shifts. However, only shifting down, or back up to the initial 0-shift works; trying to shift up from there does not bring about any notable increase in pitch.

I'm really not sure about the precision of the whole approach, as I'm still very new to electronics and programming. But, given that the 8-bit version works well, I'm just curious to see if it can be improved!
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 05, 2023, 05:42:28 AM
Quote from: anotherjim on January 05, 2023, 05:32:54 AM
I think it's always tricky to adapt code, you have to fit your mindset in with the original coders and adhere to the overall concept.

There is a quick and dirty trick to smoothing the output for this kind of thing and that is to maintain "new" output samples at the sample rate by creating average sample values. Always send the most recently read sample from the buffer to the PWM after averaging it with the previously sent sample. The resulting wave will move progressively between the "real" values from the buffer instead of repeating the same and creating a stepped waveform. It's artificial, but I think it sounds better for it.
Averaging is fairly fast for the MCU to do. The code just needs to perform a 16bit addition and divide by 2 by shifting the result right one place.

I think that this is also done in the original article. I found that including this procedure here introduced high frequency noise. I'm not sure why, but considering that it would be an optimization, I'd first like to get the shifting to work properly.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ripthorn on January 05, 2023, 02:23:53 PM
This is cool! I do quite a bit with ATTiny's and a pitch shifter has been on my list. If your issue is with resolution, I would do it the easy way and use an ATTiny841 which has the one 8 bit timer (for Timer0) and two 10-bit timers for PWM and the like. In fact, I will probably try it myself and see what happens, but I have one project whose firmware is fighting me right now that I need to polish off first. I'll read through the source code and yours and see if anything jumps out at me. Thanks for bringing this to my attention!

EDIT: Looks like the 841 doesn't have the 64MHz PLL, either. Still, some cool possibilities here
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: anotherjim on January 05, 2023, 02:31:45 PM
Things can be done to condition the input and output signals, but yes you should get the code workings before worrying about it.
If you're using the 1k/100nF PWM output filter in the article, it's very heavy-handed as it cuts all treble over 1.6Khz, but deal with that later.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 05, 2023, 02:53:47 PM
Quote from: Ripthorn on January 05, 2023, 02:23:53 PM
Thanks for bringing this to my attention!

You're very welcome. It is cool indeed. The 8-bit version is already fun.

Regarding better hardware, I'm actually working on implementing some effects on the Raspberry Pi Pico, which has great features. However, there is still a lot to learn for me there!
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ripthorn on January 05, 2023, 03:09:10 PM
The Raspberry Pi zero is what I would like to use. I have a Pico on hand for development.

One question about your code: Is there a particular reason you are using a signed char for N and not a signed float? The documentation for pow states that the exponent must be a float and I don't see you ever casting N to a float prior to the call to pow.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 05, 2023, 03:30:43 PM
Quote from: Ripthorn on January 05, 2023, 03:09:10 PM
The Raspberry Pi zero is what I would like to use. I have a Pico on hand for development.

One question about your code: Is there a particular reason you are using a signed char for N and not a signed float? The documentation for pow states that the exponent must be a float and I don't see you ever casting N to a float prior to the call to pow.

I didn't know the exponent had to be a float (edit: shouldn't that be a double?). I chose a char as it seemed to be the smallest size variable suitable. I'll change to a float to see if it makes a difference...
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: niektb on January 05, 2023, 06:24:27 PM
Since we're on the topic on increasing bit depth, may it perhaps be easier to parallel 2 pwm outputs?
http://www.openmusiclabs.com/learning/digital/pwm-dac/dual-pwm-circuits/index.html
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ripthorn on January 05, 2023, 10:34:40 PM
Quote from: Ksander on January 05, 2023, 03:30:43 PM
Quote from: Ripthorn on January 05, 2023, 03:09:10 PM
The Raspberry Pi zero is what I would like to use. I have a Pico on hand for development.

One question about your code: Is there a particular reason you are using a signed char for N and not a signed float? The documentation for pow states that the exponent must be a float and I don't see you ever casting N to a float prior to the call to pow.

I didn't know the exponent had to be a float (edit: shouldn't that be a double?). I chose a char as it seemed to be the smallest size variable suitable. I'll change to a float to see if it makes a difference...

The documentation states both base and exponent need to be floats because it supports fractional exponents. I hope that helps you. Keep us posted.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 05, 2023, 11:14:18 PM
Quote from: niektb on January 05, 2023, 06:24:27 PM
Since we're on the topic on increasing bit depth, may it perhaps be easier to parallel 2 pwm outputs?
http://www.openmusiclabs.com/learning/digital/pwm-dac/dual-pwm-circuits/index.html

yes, It was suggested also in an earlier comment. I want to look into that too!
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 05, 2023, 11:44:22 PM
Quote from: anotherjim on January 04, 2023, 03:06:03 PM
The resolution is not that bad IMHO, I have a flanger built in a Tiny84 that does ok ...

Am I correct that there is a demo of this flanger on SoundCloud? It sounds really great. Could you share some more info on how the PWM channel combination was implemented? Is it sending the 2 LSB to one channel and the 8MSB to another, and then summing these after scaling the LSB down a factor 4 with a voltage divider?
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on January 05, 2023, 11:51:14 PM
Quote from: niektb on January 05, 2023, 06:24:27 PM
Since we're on the topic on increasing bit depth, may it perhaps be easier to parallel 2 pwm outputs?
http://www.openmusiclabs.com/learning/digital/pwm-dac/dual-pwm-circuits/index.html

For some reason this response appeared in a very tiny font in my browser. Very interesting, thanks!
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: anotherjim on January 06, 2023, 04:17:11 AM
Tiny84 flanger is right here...
https://www.diystompboxes.com/smfforum/index.php?topic=110730.msg1016726#msg1016726
...and yes, it is the one found on Soundcloud.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: nodemand on January 31, 2023, 02:10:24 PM
Hi!
Great initiative!
I'm also working on a similar solution. Real time pitch shifting. If you are @home in Assembly, the following page might be of interest:

http://elm-chan.org/works/vp/report.html

ASM code if available for download at the bottom of the page...
Also, as an microcontroller, the ATtiny1616 might be a good choice.
It has a DAC, hardware multiplication support and a 20MHz internal oscillator; so no crystal needed and at the time of writing it is actually available ;-).
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: parmalee on May 21, 2023, 05:58:11 AM
Alternately, you could try this with a board implementing either the ATSAMD21 or ATSAMD51 chips--such as the Adafruit Itsybitsy boards.  With these you could have either 10 bit output (at 44.1Khz sampling rate) with the 21 or 12 bit output with the 51.  And, if you were so inclined, use I2S with an additional decoder board to get 16 bit!  (IF you opt to go this route, make sure the chip has I2S capabilities--the 48 pin (smd) version of the 51 does not.)

Caveat:  This approach will require considerable adaptation from the technoblogy code.

Incidentally, I had previously attempted to sort of "fuse" the short sample player code from technoblogy with the method described for obtaining 10 or 12 bit pwm output.  Eventually, I just went with an ATSAMD21 board as it's got way more... well, way more everything as compared with the Attiny85, and I could store multiple (short) samples on a single board and have more than one outputting simultaneously.  Also, while somewhat larger than the Attiny, these boards are (or can be, depending upon which board you go with and how many pins and such you intend to utilize) still quite small--which I needed for my particular application. *

* OK, maybe I didn't need such, but I'm a bit obsessed with fitting things into the most compact enclosure conceivable.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: patrick398 on June 20, 2023, 08:11:59 AM
I've been having some fun with this little circuit, i was wondering how easy it would be to replace the push buttons which shift the pitch and just have a pot with continuous adjustment from top of the range to the bottom?
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: patrick398 on June 21, 2023, 04:38:21 AM
I've had a go at implemented a pot to replace the two buttons but so far to no avail. I'm just doing an analogRead of the pot, mapping its value and calling it 'range' then replacing the OCR0A value with range. Not sure why it's not working (other than my feeble coding abilities)

I've made some other changes to the code so it makes some pretty wild sounds but all works fine with the buttons, just not with the pot.

Any thoughts?




/* Audio Pitch Shifter

   David Johnson-Davies - www.technoblogy.com - 11th February 2017
   ATtiny85 @ 8MHz (internal oscillator; BOD disabled)
   
   CC BY 4.0
   Licensed under a Creative Commons Attribution 4.0 International license:
   http://creativecommons.org/licenses/by/4.0/
*/

volatile uint8_t Buffer[256];             // Circular buffer
volatile uint8_t ReadPtr, WritePtr, LastPtr, New;

// Everything done by interrupts **********************************

// ADC conversion complete - save sample in buffer
ISR (ADC_vect) {
  Buffer[LastPtr] = New;
  New = ADCH + 128;
  Buffer[WritePtr] = (Buffer[WritePtr] + New)>>1;
  LastPtr = WritePtr;
  WritePtr = (WritePtr -5) & 0xFF;
}

// Timer interrupt - read from buffer and output to DAC
ISR (TIMER0_COMPA_vect) {
  OCR1A = Buffer[ReadPtr];
  ReadPtr = (ReadPtr + 5) & 0xFF;
}

// Read pot value and map to set range of pitch shift
int potValue = analogRead (0);
int range = map (potValue, 0, 1023, 18, 67);



// Setup **********************************************
 
void setup () {
  // Enable 64 MHz PLL and use as source for Timer1
  PLLCSR = 1<<PCKE | 1<<PLLE;     

  // Set up Timer/Counter1 for PWM output
  TIMSK = 0;                              // Timer interrupts OFF
  TCCR1 = 1<<PWM1A | 2<<COM1A0 | 1<<CS10; // PWM OCR1A, clear on match, 1:1 prescale
  OCR1A = 67;
  pinMode(1, OUTPUT);                     // Enable OC1A PWM output pin

  // Set up Timer/Counter0 to generate 20kHz interrupt
  TCCR0A = 2<<WGM00;                      // CTC mode
  TCCR0B = 2<<CS00;                       // /8 prescaler
  OCR0A = range;                             // set 0CR0A as mapped pot read
  TIMSK = TIMSK | 1<<OCIE0A;              // Enable interrupt
 
  // Set up ADC
  ADMUX = 2<<REFS0 | 1<<ADLAR | 7<<MUX0;  // Internal 1.1V ref, ADC2 vs ADC3, x20
  // Enable, auto trigger, interrupt, 250kHz ADC clock:
  ADCSRA = 1<<ADEN | 1<<ADSC | 1<<ADATE | 1<<ADIE | 4<<ADPS0; 
  ADCSRB = 1<<7 | 0<<ADTS0;               // Bipolar, free-running

  PCMSK = 1<<PINB0 | 1<<PINB2;            // Pin change interrupts on PB0 and PB2
  GIMSK = GIMSK | 1<<PCIE;                // Enable pin change interrupt

}
         
         
         
          // Enable pin change interrupt


void loop () {
}
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: patrick398 on July 03, 2023, 06:35:04 AM
Sorry to keep banging on about this but i read that this may be easier to do with a rotary encoder. I'm guessing that's because it works using interrupts and so does the rest of the code...
Am i getting anywhere closer with this? I haven't tested it yet but just want to know if i'm in the ball park.

Thank!

/* Audio Pitch Shifter

   David Johnson-Davies - www.technoblogy.com - 11th February 2017
   ATtiny85 @ 8MHz (internal oscillator; BOD disabled)
   
   CC BY 4.0
   Licensed under a Creative Commons Attribution 4.0 International license:
   http://creativecommons.org/licenses/by/4.0/
*/

volatile uint8_t Buffer[256];             // Circular buffer
volatile uint8_t ReadPtr, WritePtr, LastPtr, New;

const int EncoderA = 3;
const int EncoderB = 4;

volatile int a0;
volatile int c0;
volatile int Count = 0;

// Everything done by interrupts **********************************

// ADC conversion complete - save sample in buffer
ISR (ADC_vect) {
  Buffer[LastPtr] = New;
  New = ADCH + 128;
  Buffer[WritePtr] = (Buffer[WritePtr] + New)>>1;
  LastPtr = WritePtr;
  WritePtr = (WritePtr + 1) & 0xFF;
}

// Timer interrupt - read from buffer and output to DAC
ISR (TIMER0_COMPA_vect) {
  OCR1A = Buffer[ReadPtr];
  ReadPtr = (ReadPtr + 1) & 0xFF;
}

// Pin change interrupt adjusts shift
ISR (PCINT0_vect) {
  int a = PINB>>EncoderA & 1;
  int b = PINB>>EncoderB & 1;
  if (a != a0) {
   OCR0A++;
    if (b != c0) {
      OCR0A--;

    }
  }


}

// Setup **********************************************
 
void setup () {
  // Enable 64 MHz PLL and use as source for Timer1
  PLLCSR = 1<<PCKE | 1<<PLLE;     

  // Set up Timer/Counter1 for PWM output
  TIMSK = 0;                              // Timer interrupts OFF
  TCCR1 = 1<<PWM1A | 2<<COM1A0 | 1<<CS10; // PWM OCR1A, clear on match, 1:1 prescale
  OCR1A = 128;
  pinMode(1, OUTPUT);                     // Enable OC1A PWM output pin

  // Set up Timer/Counter0 to generate 20kHz interrupt
  TCCR0A = 2<<WGM00;                      // CTC mode
  TCCR0B = 2<<CS00;                       // /8 prescaler
  OCR0A = 55;                             // 17.9kHz interrupt
  TIMSK = TIMSK | 1<<OCIE0A;              // Enable interrupt
 
  // Set up ADC
  ADMUX = 2<<REFS0 | 1<<ADLAR | 7<<MUX0;  // Internal 1.1V ref, ADC2 vs ADC3, x20
  // Enable, auto trigger, interrupt, 250kHz ADC clock:
  ADCSRA = 1<<ADEN | 1<<ADSC | 1<<ADATE | 1<<ADIE | 5<<ADPS0; 
  ADCSRB = 1<<7 | 0<<ADTS0;               // Bipolar, free-running

  // Set up buttons on PB0 and PB2
  pinMode(0, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  PCMSK = 1<<PINB0 | 1<<PINB2;            // Pin change interrupts on PB0 and PB2
  GIMSK = GIMSK | 1<<PCIE;                // Enable pin change interrupt
}

void loop () {
}
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: patrick398 on July 04, 2023, 07:58:10 AM
For anyone still following, made some progress and got the rotary encoder working, kind of. It moves the pitch down but i can't seem to get it to shift the pitch up when the encoder is turned the other direction, any thoughts?

//Good settings - OCR0A = 18 + 3<<ADPS0;
//              - OCR0A = 67 + 4<<ADPS0
//              - OCR0A = 27 + 5<<ADPS0


/* Audio Pitch Shifter

   David Johnson-Davies - www.technoblogy.com - 11th February 2017
   ATtiny85 @ 8MHz (internal oscillator; BOD disabled)

   CC BY 4.0
   Licensed under a Creative Commons Attribution 4.0 International license:
   http://creativecommons.org/licenses/by/4.0/
*/

volatile uint8_t Buffer[256];             // Circular buffer
volatile uint8_t ReadPtr, WritePtr, LastPtr, New;
const int EncoderA = 0;
const int EncoderB = 2;
volatile int a0;
volatile int c0;
volatile int Count = 0;

// Everything done by interrupts **********************************

// ADC conversion complete - save sample in buffer
ISR (ADC_vect) {
  Buffer[LastPtr] = New;
  New = ADCH + 128;
  Buffer[WritePtr] = (Buffer[WritePtr] + New) >> 1;
  LastPtr = WritePtr;
  WritePtr = (WritePtr - 5) & 0xFF;
}

// Timer interrupt - read from buffer and output to DAC
ISR (TIMER0_COMPA_vect) {
  OCR1A = Buffer[ReadPtr];
  ReadPtr = (ReadPtr + 5) & 0xFF;
}

// Pin change interrupt adjusts shift
ISR (PCINT0_vect) {
  int a = PINB>>EncoderA & 20;
  int b = PINB>>EncoderB & 20;
  if (a != a0) {
   OCR0A++;
    if (b != c0) {
      OCR0A--;
    }
  }
}



// Setup **********************************************

void setup () {
  // Enable 64 MHz PLL and use as source for Timer1
  PLLCSR = 1 << PCKE | 1 << PLLE;

  // Set up Timer/Counter1 for PWM output
  TIMSK = 0;                              // Timer interrupts OFF
  TCCR1 = 1 << PWM1A | 2 << COM1A0 | 1 << CS10; // PWM OCR1A, clear on match, 1:1 prescale
  OCR1A = 67;
  pinMode(1, OUTPUT);                     // Enable OC1A PWM output pin

  // Set up Timer/Counter0 to generate 20kHz interrupt
  TCCR0A = 2 << WGM00;                    // CTC mode
  TCCR0B = 2 << CS00;                     // /8 prescaler
  OCR0A = 57;                             // 17.9kHz interrupt
  TIMSK = TIMSK | 1 << OCIE0A;            // Enable interrupt

  // Set up ADC
  ADMUX = 2 << REFS0 | 1 << ADLAR | 7 << MUX0; // Internal 1.1V ref, ADC2 vs ADC3, x20
  // Enable, auto trigger, interrupt, 250kHz ADC clock:
  ADCSRA = 1 << ADEN | 1 << ADSC | 1 << ADATE | 1 << ADIE | 5 << ADPS0;
  ADCSRB = 1 << 7 | 0 << ADTS0;           // Bipolar, free-running

// Set up buttons on PB0 and PB2
  pinMode(EncoderA, INPUT_PULLUP);
  pinMode(EncoderB, INPUT_PULLUP);
  PCMSK = 1<<PINB0 | 1<<PINB2;            // Pin change interrupts on PB0 and PB2
  GIMSK = GIMSK | 1<<PCIE;                // Enable pin change interrupt


}


void loop () {
}
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on October 27, 2023, 09:07:23 AM
I had kind of forgotten about this thread!

I have a version with a pot too, which allows varying between -12 and +12 half steps. The problem is that two ADC uses introduces a lot of additional noise. I'll post the code later on.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on October 27, 2023, 04:42:08 PM
As promised. The pot is read when the button is pressed. It works, but is too noisy...


/* Audio Pitch Shifter
 
   Based on:
   David Johnson-Davies - www.technoblogy.com - 11th February 2017
   ATtiny85 @ 8MHz (internal oscillator; BOD disabled)
   
   CC BY 4.0
   Licensed under a Creative Commons Attribution 4.0 International license:
   http://creativecommons.org/licenses/by/4.0/

   20220706 - Ksander de Winkel - included button debouncing
   20221031 - Ksander de Winkel - implemented shifting by half notes
   20230115 - Ksander de Winkel - implemented us e of a potentiometer to set shift directly
*/

// Definitions
# define NMATCH 56                          // initial compare match value to set audio write frequency
# define adc_a 0b10110111                   // ADC settings for sampling audio 0b10110111 -> gain 20; 0b10110110 -> gain 1
# define adc_b 0b10110001                   // ADC settings for sampling an external potentiometer
# define NSAMP 21

// Declarations
volatile uint8_t AudioBuffer[256];               // Circular buffer
volatile uint8_t ADCBuffer[NSAMP];
volatile uint8_t ReadPtr, WritePtr, LastPtr, New, ADCHat;
volatile uint16_t ButtonDelayCounter = 0;
volatile signed char N = 0, idx;

// Interrupt called when an ADC conversion completes
// Write ADC value to variable(s)
// An ADC conversion takes 14 clock cycles; the sampling rate will be approximately 250/14 or 17857.1Hz.
ISR (ADC_vect) {

  AudioBuffer[WritePtr] = ADCH + 128;
  WritePtr = (WritePtr + 1) & 0xFF;
 
  //AudioBuffer[LastPtr] = New;
  //New = ADCH + 128;
  //AudioBuffer[WritePtr] = (AudioBuffer[WritePtr] + New)>>1; // average present and previous reading
  //AudioBuffer[WritePtr] = (AudioBuffer[(WritePtr-1) & 0xFF] + AudioBuffer[WritePtr] + New) / 3 ;
  //LastPtr = WritePtr;
  //WritePtr = (WritePtr + 1) & 0xFF;
}

// Interrupt called on timer 0
// Read from buffer and output to DAC
// Frequency = clk / prescaler / compare_match:
ISR (TIMER0_COMPA_vect) {
 
  OCR1A = AudioBuffer[ReadPtr];
  ReadPtr = (ReadPtr + 1) & 0xFF;
  if (ButtonDelayCounter>0) ButtonDelayCounter--;                 // added KW 20220706
 
}

// Pin change interrupt adjusts shift
// The shift affects reading frequency as 8 mHz / 8 prescaler / OCR0A
// Doubling OCR0A to 2*56 = 128 means octave DOWN
// Halving OCR to 56/2 = 28 means octave UP
// Intermediate steps adjust by semitones

// Pin change interrupt to
// 1. interrupt audio processing,
// 2. sample the potmeter,
// 3. set N
// 4. restart audio processing

ISR (PCINT0_vect) {

  if (ButtonDelayCounter == 0) {

    readPot();
   
  }
                                         
}

void readPot(){
 
  initADCb();
 
  while (ADCSRA & (1 << ADSC) ){/*do nothing*/}; // wait till any running conversion is complete
 
  for(int i = 0 ; i < NSAMP; i++){
    ADCSRA |= (1 << ADSC);         // start ADC measurement
    while (ADCSRA & (1 << ADSC) ){/*do nothing*/}; // wait till conversion complete
    ADCBuffer[i] = ADCH;          // write value in buffer
  }

//  ADCHat = (ADCBuffer[4]+ADCBuffer[3]+ADCBuffer[2])/3;

  qsort(ADCBuffer, NSAMP, sizeof(ADCBuffer[0]), compare);
  idx = floor(NSAMP/2);
  ADCHat = ADCBuffer[idx];
 
  N = map(ADCHat,0,255,-12,13);
  OCR0A = round( NMATCH / pow(1.0595,(int)N) ) - 1;
   
  ButtonDelayCounter = 500;             // added KW 20220706

  initADCa();
 
}

int compare (const void * a, const void * b)
{
  return ( *(int*)a - *(int*)b );
}

void initPWM() {
 
  // Set up Timer1 for PWM
 
  PLLCSR = 1<<PCKE | 1<<PLLE;             // Enable 64 MHz PLL and use as source for Timer1     
  TIMSK = 0;                              // Timer interrupts OFF
  TCCR1 = 1<<PWM1A | 2<<COM1A0 | 1<<CS10; // PWM OCR1A, clear on match, 1:1 prescale
  OCR1A = 128;                            // Inital PWM output value
  pinMode(1, OUTPUT);                     // Enable OC1A PWM output pin
 
}

void initAudioOut() {
 
  // Set up Timer0 to generate audio output interrupt
 
  TCCR0A = 2<<WGM00;                      // CTC mode
  TCCR0B = 2<<CS00;                       // /8 prescaler
  OCR0A = NMATCH - 1;                     // 17.9kHz interrupt (1M/56)
  TIMSK = TIMSK | 1<<OCIE0A;              // Enable interrupt
 
}


void initButtons() {
 
  // Set up pushbuttons on PB0 and PB2
   
  pinMode(0, INPUT_PULLUP);     // Pin PB0, initialized HIGH
  PCMSK = 1<<PINB0;             // Pin change interrupts on PB0 (for PB2 add " | 1<<PINB2")
  GIMSK = GIMSK | 1<<PCIE;      // Enable pin change interrupt

}

void initADCa() { // Set up ADC for audio
 
  // ADMUX register
 
  // 1<<REFS2 with next line...
  // 1<<REFS1 with next line...
  // 0<<REFS0 Internal 2.56v reference
  // 6<<REFS0 as above(?)
  // 1<<ADLAR left-adjusted data register
  // 7<<MUX0  differential inputs ADC2 (PB4) and ADC3 (PB3) with a gain of x20
  // Above is set in adc_a

  ADMUX = adc_a;
   
  // ADCSRA register

  // 1<<ADEN   ADC enabled
  // 1<<ADSC   Start conversions
  // 1<<ADATE  Auto triggering
  // 1<<ADIE   Enable the ADC conversion interrupt
  // 5<<ADPS0  Select a prescaler of divide by 32, giving an ADC clock of 8mHz/32 = 250kHz

    ADCSRA =
        (1 << ADEN)  |     // Enable ADC
        (1 << ADATE) |     // auto trigger enable
        (1 << ADIE)  |     // enable ADC interrupt
        (5 << ADPS0);      // Prescaler = 32

    // ADCSRB register

    //1<<BIN    Bipolar input mode (BIN = 7).
    //0<<ADTS0  Free running mode.
   
    ADCSRB = 1<<7 | 0<<ADTS0;               // Bipolar, free-running
   
    sei(); // enable interrupts
   
    ADCSRA |= (1 << ADSC); // start conversions
}

void initADCb() { // Set up ADC for potmeter
 
  // ADMUX register
 
  // 1<<REFS2 with next line...
  // 1<<REFS1 with next line...
  // 0<<REFS0 internal 2.56v reference
  // 6<<REFS0 as above(?)
  // 1<<ADLAR left-adjusted data register
  // 1<<MUX0  single ended input ADC1 (PB2)
  // above is set in adc_b

  ADMUX = adc_b;
   
  // ADCSRA register

  // 1<<ADEN   ADC enabled
  // 1<<ADSC   start conversions - set at end
  // 1<<ADATE  auto triggering
  // 1<<ADIE   enable the ADC conversion interrupt
  // 5<<ADPS0  select a prescaler of divide by 32, giving an ADC clock of 8mHz/32 = 250kHz

  ADCSRA =
      (1 << ADEN)  |     // Enable ADC
      (0 << ADATE) |     // auto trigger enable
      (0 << ADIE)  |     // enable ADC interrupt
      (0 << ADPS0);      // Prescaler = 32
     
  // ADCSRB register
 
  //0<<BIN    unipolar input mode (BIN = 7)
  //0<<ADTS0  free running mode
       
  ADCSRB = 0<<7;
}

void setup () {

  initPWM();      // Start PWM
  initAudioOut(); // Start sending samples to PWM
  initButtons();  // Start button interrupt
  readPot();      // Get initial reading for pitch shift
 
}

void loop () {
}

Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on December 02, 2023, 06:26:32 AM
With some lessons learned from the Attiny85 'Tiny Ensemble' Chorus thread, I now got the 10-bit chromatic pitch shifter to work. Is it better than the 8-bit version? Well, a bit or two.

It works differently than before:


There are some downsides compared to the 8-bit version: the 16-bit samples take up more memory, so the buffer is smaller. It holds some 10ms of audio. Given that the low e string note on a guitar has a period of about 12ms, this means that the buffer can't hold even one period. This becomes audible when shifting the pitch, in the form of a popping sound. The other downside is that the reading and outputting to PWM seems to be too slow. I do not know why. I corrected for this by fine-tuning the OCR1A values. I did this by using a 440Hz test signal; pitching it through the range of +/-12 semitones, measuring the output frequency and then loosely optimizing the OCR1A value for each. Expected frequencies are given by the equation: 440/(2^(1/12)^N).

On the upside, this scheme may be quite easily adapted to higher bit-rates, by oversampling the ADC and setting higher bit rates for the PWM pins.

Anyway, for anyone interested, here is the code:

/* 10-bit Audio Pitch Shifter

   Approximately chromatic glitchy 10-bit pitch shifter by Ksander de Winkel
   Thanks to the folks at DIYstompboxes.com!
 
   Based on projects by David Johnson-Davies - www.technoblogy.com - 11th February 2017
   ATtiny85 @ 8MHz (internal oscillator; BOD disabled)
   
   CC BY 4.0
   Licensed under a Creative Commons Attribution 4.0 International license:
   http://creativecommons.org/licenses/by/4.0/

*/
#include <avr/pgmspace.h>
#include <util/delay.h>
#define NWRAP 180
#define F_CPU 8000000UL

volatile uint8_t A, B, WritePtr, ReadPtr, N = 12;
volatile uint16_t Sample, Buffer[NWRAP+1];

const int EncoderA = 0;
const int EncoderB = 2;

volatile int a0;
volatile int c0;

// Pitch rate
static const uint8_t ocr[] PROGMEM = {103,97,92,86,82,77,73,68,65,61,57,54,51,47,45,43,40,38,35,33,32,30,29,26,25};

// Everything done by interrupts

// Save sample in buffer when ADC conversion is complete
ISR (ADC_vect) {
 
  Buffer[WritePtr] = (ADC + Sample) >> 1;
  WritePtr = (WritePtr + 1);
  if (WritePtr>NWRAP) WritePtr = 0;
  Sample = Buffer[WritePtr];

}

// Timer interrupt
ISR (TIMER0_COMPA_vect) {

  A = (Buffer[ReadPtr]) & 0x1F;
  B = (Buffer[ReadPtr] >> 5) & 0x1F;
 
  OCR1A = A;
  OCR1B = B;
 
  ReadPtr = (ReadPtr + 1);
  if (ReadPtr>NWRAP) ReadPtr = 0;
 
}

// Called when encoder value changes
void ChangeValue (bool Up) {
  N = max(min(N + (Up ? 1 : -1), 24), 0);
  OCR0A = pgm_read_byte(&ocr[N]);
}

// Pin change interrupt service routine
ISR (PCINT0_vect) {
 
  int a = PINB>>EncoderA & 1;
  int b = PINB>>EncoderB & 1;

  if (a != a0) {              // A changed
    a0 = a;
    if (b != c0) {
      c0 = b;
      ChangeValue(a == b);
    }
  }
}

// Setup

void setup () {
 
//  // Enable 64 MHz PLL and use as source for Timer1
//  PLLCSR = 0<<PCKE | 1<<PLLE ;

  // Set up timer/PWM items
  PLLCSR |= (1 << PLLE); //Enable PLL

  //Wait for PLL to lock
  _delay_us(100);
 
  while ((PLLCSR & (1<<PLOCK)) == 0x00)
    {
        // Do nothing until plock bit is set
    }

  // Enable clock source bit
  PLLCSR |= (1 << PCKE);     

  // Set up Timer/Counter1 for PWM output
  TIMSK = 0;                              // Timer interrupts OFF
  TCCR1 = 1<<CTC1 | 1<<PWM1A | 2<<COM1A0 | 1<<CS10; // CTC mode | PWM OCR1A | clear on match | /1 prescaler
  GTCCR = 1<<PWM1B | 2<<COM1B0;
  OCR1A = 15; // High bits
  OCR1B = 15; // Low bits
  OCR1C = 31;
  pinMode(1, OUTPUT);                     // Enable OC1A PWM output pin (PB1)
  pinMode(4, OUTPUT);                     // Enable OC1B PWM output pin (PB4)
 
  // Set up Timer/Counter0 to generate interrupt
  TCCR0A = 2<<WGM00;                      // CTC mode
  TCCR0B = 2<<CS00;                       // /8 prescaler
  OCR0A = 51;                             // 17.9kHz interrupt (1M/56)
  TIMSK = TIMSK | 1<<OCIE0A;              // Enable interrupt
 
  // Set up ADC
  ADMUX = 0<<REFS0 | 0<<ADLAR | 3<<MUX0;  // Vcc as Vref | right-align | ADC3 (PB3)
  ADCSRA = 1<<ADEN | 1<<ADSC | 1<<ADATE | 1<<ADIE | 5<<ADPS0; // Enable | Start conversion | auto trigger | interrupt enable | 250kHz ADC clock:
  ADCSRB = 0<<7 | 0<<ADTS0;               // Single-ended, free-running

  // Set up buttons on PB0 and PB2
  pinMode(EncoderA, INPUT_PULLUP);
  pinMode(EncoderB, INPUT_PULLUP);

  // Configure pin change interrupts
  PCMSK = 1<<EncoderA;
  GIMSK = 1<<PCIE;                       // Enable interrupt
  GIFR = 1<<PCIF;                        // Clear interrupt flag
 
}

void loop () {
}
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: ElectricDruid on December 02, 2023, 11:59:53 AM
You're starting to really push these little chips, Ksander! Excellent stuff, and well done for sticking with it. 8)

I'll have to get some AVRs and have a play myself.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on December 02, 2023, 12:47:12 PM
Yes, please do and share your experiences and wisdom  :)

These IC's are quite limited in what they can do, but have the size of an op-amp and, even though it is still a challenge, to me at least, the datasheet and arduino environment are just accessible enough for a beginner. With a little persistence, you can make them do a lot. For instance, I also have one act as a waveform generator, like on one of your dedicated IC's!
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: parmalee on December 27, 2023, 01:46:01 AM
Thank you for all the work you've put into this, Ksander!

Upon reviewing all of the commercially available pedals that do pitch shifting--well, detuning to be more precise--I have discovered that none of them will shift more than + or - 30 cents.  For what I'm doing right now (with a harmonium) I need the ability to shift certain tones upwards precisely 50 cents (a quarter-tone) with the flick of a switch.  Well, a momentary button will likely prove more efficient.  Regardless, this is great! 

I can easily adapt your code and follow in the footsteps of some of my favorite nutters in the Arabic world who've made all sorts of wacky modifications to pianos, accordions and the like in order to achieve this feat.  So, thanks again!


Edit:  I was attempting to do something similar with an ATSAMD51 board, but I find working with the timers considerably more complicated.  Also the datasheets (quite massive) seem to be a whole lot less thorough than those for the Attiny and Atmega series.  Kinda frustrating as those chips seem to have a lot more potential for working with higher fidelity audio.
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: parmalee on December 27, 2023, 03:05:25 AM
Expanding upon that a bit:

The quarter tone/50 cent ratio (in 24 TET) is approximately 1.0293, so with an OCR0A value of 101 (102), a 50 cent increment would be 104.989.  104 (105) ought to be just fine for all but those with some sort of superhuman perfect pitch, I figure.  LaMonte Young might cringe at that, but that's his problem.  :icon_biggrin:
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: Ksander on December 27, 2023, 03:12:21 PM
Quote from: parmalee on December 27, 2023, 03:05:25 AMExpanding upon that a bit:

The quarter tone/50 cent ratio (in 24 TET) is approximately 1.0293, so with an OCR0A value of 101 (102), a 50 cent increment would be 104.989.  104 (105) ought to be just fine for all but those with some sort of superhuman perfect pitch, I figure.  LaMonte Young might cringe at that, but that's his problem.  :icon_biggrin:

It's a great idea to use a momentary button to get such shifts. Have you built the circuit yet?
Title: Re: Programming a 10bit Attiny85 pitch shifter
Post by: parmalee on December 27, 2023, 04:16:25 PM
Quote from: Ksander on December 27, 2023, 03:12:21 PMIt's a great idea to use a momentary button to get such shifts. Have you built the circuit yet?

I'm going to try to put it together this weekend--have to go pick up a rescue dog we're adopting (hopefully) a couple of hours away from us.  Priorities!

Apparently the custom in Arabic music is to implement some sort of device to drop the pitch of certain notes 50 cents as needed.  If you listen to pieces by Oum Khulthum, Warda, et al from the late 60s/early 70s you can often hear this on whatever transistor organs they happened to be using.  With pianos and accordions they tend to make various sorts of mechanical modifications to their instruments.  I'm not inclined towards doing that with my harmonium, I'll stick with electronic means.

Che Chen had one of his guitars modded with extra frets to play various quarter tone intervals, drawing inspiration largely from Mauritanian music.  Lot of good videos of him playing said guitar in 75 Dollar Bill, but I've always enjoyed this one particularly: