Shop OBEX P1 Docs P2 Docs Learn Events
Karplus-Strong on the Propeller? — Parallax Forums

Karplus-Strong on the Propeller?

jeff-ojeff-o Posts: 181
edited 2013-06-18 03:29 in Propeller 1
Hi everyone, I searched the forums and found a mention here and there about Karplus-Strong, but it would seem that no-one has done it on the Propeller yet.

Karplus-Strong is a head-slappingly simple way of modeling the behavior of a plucked string. See the wiki entry here:

http://en.wikipedia.org/wiki/Karplus-Strong_string_synthesis

Basically, a noise burst is sent through a delay line and then a filter. The resulting waveform is then added back into a loop and sent to an output. Each time the waveform passes through the loop, it becomes more "filtered" and closer to the fundamental frequency determined by the total delay of the loop.

This algorithm was first modeled on 80's era computers and VLSI chips, so surely the Propeller is up to the task, right? Sadly my programming skills are not where they need to be to pull this off, but I'm hoping someone here can manage it, or at least point me in the right direction.

Here are some other resources that may be useful:

https://ccrma.stanford.edu/realsimple/faust_strings/faust_strings.pdf
http://ccrma.stanford.edu/~jos/pasp/Karplus_Strong_Algorithm.html

The end goal is some sort of musical instrument, like a guitar or harp. However, this algorithm can be used to simulate drums or almost any other plucked or struck instrument, with the right parameters.

Thanks for any help!
«13456

Comments

  • ericballericball Posts: 774
    edited 2011-04-27 08:25
    Hmm... I think the major limitation will be the lack of a hardware multiplier on the Propeller. A simple 16x16bit multiply is a little over 128 cycles (in PASM), or 625kHz at 80MHz, or a 14 tap FIR filter at 44.1kHz. Not bad, although the real implementation will be lower.

    Of course, it's possible to make filters which don't require full multiplies, which would be even faster.
  • LeonLeon Posts: 7,620
    edited 2011-04-27 08:39
    I tried an implementation for an ADI 16-bit DSP many years ago, it sounded quite realistic. That was a 30 MIPS device with hardware multiply, X and Y memories and a MAC.
  • jeff-ojeff-o Posts: 181
    edited 2011-04-27 09:03
    According to the wikipedia entry, the filter in the original algorithm could be done with just shift and add operations:
    In the original algorithm, the filter consisted of averaging two adjacent samples, a particularly cheap filter that can be done without a multiplier (just shift and add operations).

    Of course, later implementations of the algorithm require fancier math.

    Another quote I found interesting is about wavetable modification:
    Alex Strong developed a superior wavetable-modification method for plucked-string synthesis, but only published it as a patent.

    Would that be a viable alternative? There's far less information on that method, however.
  • jeff-ojeff-o Posts: 181
    edited 2011-04-27 09:07
    ericball wrote: »
    Hmm... I think the major limitation will be the lack of a hardware multiplier on the Propeller. A simple 16x16bit multiply is a little over 128 cycles (in PASM), or 625kHz at 80MHz, or a 14 tap FIR filter at 44.1kHz. Not bad, although the real implementation will be lower.

    Of course, it's possible to make filters which don't require full multiplies, which would be even faster.

    That could be good enough, since a guitar string has fundamentals that max out at 659Hz and harmonics that max out at 5 or 6kHz. An FIR filter would be nice because it allows for fractional delays.
  • jeff-ojeff-o Posts: 181
    edited 2011-04-27 10:52
    More useful info, in this project Karplus-Strong was implemented on an ATMega664, and written in C:

    http://courses.cit.cornell.edu/ee476/FinalProjects/s2009/cgg27_jpv23/cgg27_jpv23/webpagef.htm

    And here, using an atmega32:

    http://instruct1.cit.cornell.edu/courses/ee476/FinalProjects/s2007/apb36_yn47/ECE_476_Website.html
  • lonesocklonesock Posts: 917
    edited 2011-04-27 12:07
    Try this...no fractional delay yet. It is set up for the demo-board, using pins 10 & 11 to control the headphone out. It uses period instead of frequency...this is just a rough proof of concept.

    Jonathan
  • jeff-ojeff-o Posts: 181
    edited 2011-04-27 13:45
    lonesock wrote: »
    Try this...no fractional delay yet. It is set up for the demo-board, using pins 10 & 11 to control the headphone out. It uses period instead of frequency...this is just a rough proof of concept.

    Jonathan

    Woohoo! I know what I'll be doing tonight!!
  • jeff-ojeff-o Posts: 181
    edited 2011-04-27 20:34
    Well this is pretty damn fantastic. Works right off the bat! I guess the functionality to add now is the fractional delay and the wire tension - that is, how long it "vibrates" before fading away. I noticed that lower notes already ring longer than high ones, just like a real string would do!

    In the demo it cycles through 15 frequencies. I tried subbing in values but I'm not exactly sure what to put in there.

    Thanks a million!
  • prof_brainoprof_braino Posts: 4,313
    edited 2011-04-28 00:21
    Please keep us posted on your progress for anychanges or imporvements. This is a cool item!
  • jeff-ojeff-o Posts: 181
    edited 2011-04-28 04:39
    Please keep us posted on your progress for any changes or improvements. This is a cool item!

    Definitely. So far I've managed to import the code into 12blocks so I can play around with values more easily. The code itself is a step or two above my head so I'm looking forward to any more work lonesock does with it!
  • lonesocklonesock Posts: 917
    edited 2011-04-28 08:23
    Thanks. I'm playing with the next revision right now. Fractional delays work, and I'm working on automatically adding in a period doubler (reinforcing with the negative of the delay sample puts it's 180 degrees out of phase, dropping the frequency by 1/2, without having to double the delay buffer).

    You can already change the sustain length...search for the word "decay" in the PASM source.

    The period that you pass to the pluck routine is in fixed point format. I think in that version you have 4 fractional bits, so basically the period would be N / 16, to within the nearest 16th of a sample. In this next version there is a constant to select how many fractional bits you care about.

    Hopefully I can upload the next version within 1/2 hour or so.

    Jonathan
  • jeff-ojeff-o Posts: 181
    edited 2011-04-28 08:33
    lonesock wrote: »
    Thanks. I'm playing with the next revision right now. Fractional delays work, and I'm working on automatically adding in a period doubler (reinforcing with the negative of the delay sample puts it's 180 degrees out of phase, dropping the frequency by 1/2, without having to double the delay buffer).

    You can already change the sustain length...search for the word "decay" in the PASM source.

    The period that you pass to the pluck routine is in fixed point format. I think in that version you have 4 fractional bits, so basically the period would be N / 16, to within the nearest 16th of a sample. In this next version there is a constant to select how many fractional bits you care about.

    Hopefully I can upload the next version within 1/2 hour or so.

    Jonathan

    Wow, sounds great! I'm looking forward to trying it out. Tonight my goal is to get four separate "strings" running on four cogs and sending output to four different pins for further mixing and analog filtering.
  • lonesocklonesock Posts: 917
    edited 2011-04-28 08:59
    Here it is...there are some notes at the top of the Spin file, explaining what I think I'm doing [8^)

    Jonathan
            Karplus-Strong on the propeller
            (sounds like a plucked string)
    
            Jonathan "lonesock" Dummer
    
            NOTES:
    
            * use any buffer size you want (no power-of-2-restriction)
    
            * buffer uses words
    
            * fraction_bits must be > 0
    
            * the period of the plucked string is specified in fixed-point
              - if 'fraction_bits' = 4, then you should use period * 2^4,
                and the code will use the period to the nearest 1/16th of
                a sample.
              - the exact period isn't precisely determined yet..I still
                have to do some math regarding the simple filtering I used.
    
            * this has an automatic period doubler
              - when the requested period is larger than the delay buffer,
                then the code halves the delay period, and samples the
                negative of the delay buffer, sampling 180 degrees out of
                phase, effectively doubling the period.  If the requested
                period is > 2x the buffer size, it is clamped.
              - the period doubling sounds more muted than the original
                version, which is why it is only used once you hit the
                limit of the sample buffer.
              - the transition frequency = sample_rate / buf_size, or when
                the fixed-point period = buf_size << fraction_bits
    
            * you can change the sustain of the string with 'default_decay'
              - implemented as scaling the feedback by (2^N - 1) / (2^N)
              - the higher this number...the longer it will ring
    
            TODO:
    
            * calculate the period from a requested frequency
    
            * allow changing the decay dynamically
    
            * simulate multiple strings in a single cog
    
  • jeff-ojeff-o Posts: 181
    edited 2011-04-28 09:14
    lonesock wrote: »
    Here it is...there are some notes at the top of the Spin file, explaining what I think I'm doing [8^)

    (code)

    Jonathan

    Oh, it's like magic in text format. I wonder if I could skip out early and go home... ;)
  • lonesocklonesock Posts: 917
    edited 2011-04-28 09:17
    [8^)

    Or do what I do...start a work project using the propeller, so I can play with it all day!

    Jonathan
  • jeff-ojeff-o Posts: 181
    edited 2011-04-28 10:00
    lonesock wrote: »
    [8^)

    Or do what I do...start a work project using the propeller, so I can play with it all day!

    Jonathan

    Someday I may just get a chance to do that. Someday...

    Also, I think I owe you a peanut butter sandwich or two. ;)
  • lonesocklonesock Posts: 917
    edited 2011-04-28 12:01
    [8^)

    I once jokingly told someone they could pay me in chocolate milk, and a week later some arrived...I didn't know you could drop ship it from amazon.com!

    Jonathan
  • jeff-ojeff-o Posts: 181
    edited 2011-04-28 14:24
    lonesock wrote: »
    [8^)

    I once jokingly told someone they could pay me in chocolate milk, and a week later some arrived...I didn't know you could drop ship it from amazon.com!

    Jonathan

    Well, as far as sandwiches are concerned the best I could do on amazon is a recipe book. Or a sandwich press. :D
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-04-28 14:30
    A friend of mine once mailed me a baloney sandwich. He didn't owe me anything, 'just thought I might be hungry and knew I couldn't get my favorite brand of baloney where I was living at the time. Unfortunately and despite, I'm sure, copious quantities of nitrates in the meat, the baloney had turned by the time it arrived. The sandwich was pretty well flattened, too.

    -Phil
  • HannoHanno Posts: 1,130
    edited 2011-04-28 21:29
    Very cool Jonathan and Jeff!
    Nice to have different sound generators on the Prop.
    Hanno
  • jeff-ojeff-o Posts: 181
    edited 2011-04-28 21:57
    Hanno wrote: »
    Very cool Jonathan and Jeff!
    Nice to have different sound generators on the Prop.
    Hanno

    All credit should go to Jonathan! I'm playing around with it right now. I *think* I managed to calculate the right "Period" based on the frequency I want in Excel, then matching it to an online tuning fork. Things aren't matching up quite right so I think I must have missed something. I'm using Period=((1/frequency)*2^fraction_bits)*100000 at the moment, it give results that are sorta close. Of course, it's also 1am and I'm hungry so I might be way off.
  • lonesocklonesock Posts: 917
    edited 2011-04-28 22:38
    Thanks. (and Baloney certainly beats chocolate milk in the funny + ugh department [8^)

    The period should be something close to :

    period_fixed_point = sample_rate * 2^fraction_bits / frequency

    It will be off a bit (I'm guessing), because of the filtering I'm using when sampling the delay buffer...I will be sitting down to try to figure out the exact equation soon, but that should get you started. I should just have the PASM code timestamp the last 0-crossings, and compute the frequency for you (using the actual sample rate, etc.)

    Jonathan
  • jeff-ojeff-o Posts: 181
    edited 2011-04-29 05:01
    Ah, that formula works much better. And, really quite accurate indeed!

    With this info I can generate a table that contains all the fret positions and their associated periods. I may hold off, however, if the plan is to ultimately use frequency and not period as the input value.
  • lonesocklonesock Posts: 917
    edited 2011-04-30 10:51
    OK, here's the next version with some musical focus. The little demo code now defines a table of 12 periods specifying the lowest octave specified by MIDI notes. For each octave above that, the frequency is doubled so the period is halved. The code just walks up the full scale of MIDI notes (0 = C-1, 69 = A4). Any notes that are too low are clamped to the longest period available (so they are stuck at some wrong frequency...easy to tell what's happening, but probably not desirable in an end application!). Anyway, with this example you should be able to do some simple plucked string melodies.

    Jonathan
  • jeff-ojeff-o Posts: 181
    edited 2011-04-30 12:41
    Excellent! I've added another PUB that lets you choose the MIDI note you want to play:
    PUB playnote( note, volume )| p
      p := note_periods[note//12] >> (note / 12)
      control := (p << 5) | (volume <# 31)
    

    And yeah, I see what you mean regarding the low notes, LOL.
  • jeff-ojeff-o Posts: 181
    edited 2011-04-30 22:57
    Here's an interesting thing, I don't know if you noticed the same. I can play notes accurately down to 32.703 Hz (MIDI 24), but no lower. At 23 and lower, the notes actually play higher than they ought to! The result is the same whether I use the "playnote" routine or plug the right numbers directly into the fixed point period of "pluck." Hardware or software issue? Not that is matters so much; neither a bass (using standard tuning) nor a guitar go down that low.
  • jeff-ojeff-o Posts: 181
    edited 2011-05-01 22:55
    OK! So I rigged up some hardware so that four pins from the propeller are fed into a mixer, amplified, etc. For this experiment I'd like to run four "mono" KS objects, each on its own cog, each feeding a different pin. I made the code mono without any troubles, but the notes cut each other off when playing. So I figure there's a bit of in-fighting over memory space or variables. Have you got a clever way of running four different objects at once so they don't interfere with each other?
  • lonesocklonesock Posts: 917
    edited 2011-05-02 06:15
    Yep, I did notice the octave jump...the definitely has to do with the period doubling, and I have more work to do on that front.

    The attached zip contains the latest code, with the demo portion of the object split out, and it uses an array of 2 mono Karplus-Strong objects, so it should be able to scale to 4 objects just fine. This demo shows the left and right channels running at independent sampling rates. (Note: I had to change how each note's period is calculated to be able to handle the sampling rate specified at run-time instead of compile-time.)

    You can pass in a value to the start routine to decide how to use the doubler: 0 means never, 1 means always switch, and any other value means always use the doubler. You can hear the difference in the sound (off has a fuller sound, on gets a lower note...I was hoping the automatic switch would give me the best of both worlds, but unless I can find th problem I'll just have to disable it).

    Jonathan
  • jeff-ojeff-o Posts: 181
    edited 2011-05-02 07:10
    lonesock wrote: »
    Yep, I did notice the octave jump...the definitely has to do with the period doubling, and I have more work to do on that front.

    The attached zip contains the latest code, with the demo portion of the object split out, and it uses an array of 2 mono Karplus-Strong objects, so it should be able to scale to 4 objects just fine. This demo shows the left and right channels running at independent sampling rates. (Note: I had to change how each note's period is calculated to be able to handle the sampling rate specified at run-time instead of compile-time.)

    You can pass in a value to the start routine to decide how to use the doubler: 0 means never, 1 means always switch, and any other value means always use the doubler. You can hear the difference in the sound (off has a fuller sound, on gets a lower note...I was hoping the automatic switch would give me the best of both worlds, but unless I can find th problem I'll just have to disable it).

    Jonathan

    Looks great! I can see from the demo how you'd go about adding more pins. Do they need to be different sampling rates, or can they be the same? I can't wait till I get home and can try it out!
  • lonesocklonesock Posts: 917
    edited 2011-05-02 07:25
    They can all be the same sample rate, however, if you were using one cog per virtual guitar string (for example), you could keep the buffer size constant, but change the sample rate for each string to get the needed range. The lowest frequency you can play is ~ sample_rate / buffer_size. You could tune the sample rate so that each string starts at E (~82.4 Hz), A, D, G, B, E (~330 Hz). Just an idea to minimize buffer space.

    Jonathan
Sign In or Register to comment.