a very basic framework for playing music

https://docs.google.com/open?id=0B9tg78cq3y-pWXo2dUVOeEhJNkE

this is a fairly simple framework for playing musical notes with SFXR. it is based on the drum machine example.

the function note() will play notes. at the moment is is very primitive- all it will do is take a number value, in terms of semitones above (or, for negative values, below) C3, and the waveform to use. it also has, in this example, a kick and hat sound which can be used.

if someone else has time, making it so that it checks a table of frequency translations that is generated at setup(), rather than doing the 0.19 * 2^24*freq every time it plays a new note would be just great. maybe even make it so that you can enter in a sting such as note_c#4 rather than 14.

i will be making a program that allows you to write songs now, and will post that once it is complete. that program will plrobably include options for note length/volume and such.

Before attacking your precalculation problem let’s get some existing things straight, perhaps I don’t understand SFXR properly …

From your code:

StartFrequency = 0.19 * 2^(freq/24)

  1. freq seems to be a relative note number rather than a frequency.

StartFrequency = 0.19 * 2^(rel_note/24)

  1. You say it is relative to C3 which has a frequency of about 130Hz.

StartFrequency = 0.13 * 2^(rel_note/24)

(Frequency is given in kHz, right?)

  1. An octave is made up of 12 semitones.

StartFrequency = 0.13 * 2^(rel_note/12)

If you’re writing a program to make music, you may base your note numbers on MIDI numbers, so A4 (I call THAT a reference, what the hell is C3?) is number 69.


function freqOfMidiNote(note_number)
    return 0.44 * 2 ^ ((note_number - 69) / 12)
end

While I’m at it, you can now make a table of frequencies:


frequencies = {}
for i = 0, 100 do
    frequencies[i] = freqOfMidiNote(i)
end

The freq is just a note number, not an actual frequency. I gave it at name because i wanted to avoid mixing it up with the function name. SFXR uses a 0-1 scale rather than hertz, which is why the math seems counterintuitive.

I spent about an hour with a tuner trying to nail down the numbers and I chose C3 because it was the absolute closest to a round value- its frequency in SFXR is 0.1902. To make the equation follow midi scale, simply change the frequency calculation to 0.19 *2((freq-60)/24)

I’m not having a problem with making a frequency table, persay. I just didn’t feel like doing it. :stuck_out_tongue: I am working on a program that enables you to write midi-like songs with this system at the moment.

Hi folks,

I too have spent many hours tuning SFXR… :slight_smile: I found the magic formula to equate keyboard notes with pitches. It’s part of my ABCplayer, which reads ABC tunes - the ASCII equivalent of MIDI. Thread with links to code here: http://twolivesleft.com/Codea/Talk/discussion/225/abcplayercodea-play-music-in-your-project#Item_46

Have a look and try it out, let me know if you would like to collaborate. Next I plan to work on different timbres an multivoicing using the ABC standard.

I thought an authoritative answer could be a valid reason to bring this thread back from the dead, be it just for education or further inspiration. I also had to correct myself, it bugged me.

The core line in SFXR is this:


fperiod=100.0/(p_base_freq*p_base_freq+0.001);

Remember that p_base_freq is expressed in a range of 0…1. Examining the code further reveals that fperiod is not exactly the period, but 8 times supersampled. Let me reflect this:


8*fperiod=100.0/(p_base_freq*p_base_freq+0.001);

Also, fperiod is expressed in terms of the sample rate, that means that at a sample rate of 44100 Hz a period value of 44100 means a sound of 1 Hz. It means that fperiod = SR/f, with f = frequency of the sound and SR = sample rate.


8*SR/f=100.0/(p_base_freq*p_base_freq+0.001);

Reordering:


f/SR=8/100*(p_base_freq*p_base_freq+0.001)

(1) p_base_freq=sqrt(f/SR*100/8-0.001)

You can use the formula (1) directly, with SR = 44100, to calculate the correct value to feed into SFXR.

Quick check: With f = 130 Hz (note C3) the result is p_base_freq = 0.19. KMEB is right. We have a formula for converting a frequency to an SFXR base frequency value.

Next step: Let’s ignore the slight offset of 0.001 and write the square root as a power.


(2) p_base_freq=(f/SR*100/8)^(1/2)

Express the frequency using a MIDI note. The basics are:


f=fN*2^((n-N)/12)

with
n  : MIDI note
N  : base note for this formula (your choice)
fN : frequency of fN

use for example:
N  = 69 (note A4)
fN = 440

or as KMEB did:
N  = 48 (note C3)
fN = 130

Insert f into (2) and simplify:


p_base_freq=(fN*2^((n-N)/12)/SR*100/8)^(1/2)

(3) p_base_freq=(2^((n-N)/24)*(fN/SR*100/8)^(1/2)

Using KMEB’s choice we get (keep in mind that n is the MIDI note number, therefore n-48, whereas KMEB sets C3 to number 0):


p_base_freq=(2^((n-48)/24)*0.19

Phew, it’s time to say “and that’s it”.

Wow, @Codeslinger, I’m impressed you tracked down how sfxr works out that value. I had fudged it by finding the 0.5 sfxr input to be A440 and tuning a semitone increment by ear to a multiplier of 1.0296. Your formula makes more sense, so I’ll merge it into my code, if that’s ok.

@Codeslinger very nice analysis, I’m sure that will be helpful for future musical endeavours with codea :slight_smile:

Wonderful analysis! I knew if I sat long enough someone would be bothered enough to go reverse-engineer the whole sfxr thing! Thank you, and bravo!

This needs to go in the wiki.

Hi @Codeslinger, I don’t suppose we could call you back to look at sfxr’s SustainTime? :slight_smile: It’s not in seconds, and I had to take the square root to get manageable durations, but that makes it too short…

I’m afraid to come up with an unsatisfying intermediate analysis.

The core lines in SFXR are these:


env_length[0]=(int)(p_env_attack*p_env_attack*100000.0f);
env_length[1]=(int)(p_env_sustain*p_env_sustain*100000.0f);
env_length[2]=(int)(p_env_decay*p_env_decay*100000.0f);

If you set attack and decay to zero and sustain to 1, SFXR will generate 100000 samples (precisely, it will generate 100003 samples, but that doesn’t matter here). At a rate of 44100 samples per seconds the duration will be a bit more than 2 seconds.

I just compiled SFXR right out of the Codea Runtime to test it and I was right, at least on my Linux system. The formula here is:


with d: duration in seconds

p_env_sustain = sqrt(d * 0.441)

where 0.441 = sample rate / 100000

This is difficult to recreate with Codea’s Example “Sounds Plus” in the advanced pane, even you “purify” the playSound function. Don’t know the reason yet.

Oh wow, @Codeslinger, that’s interesting. Thanks for looking into this. So we will have to take into account the sample size…

On another note (no pun intended) I notice that the formula you and @KMEB worked on is a bit out of tune. I think you need that 0.001 in there.

Thanks!

Thanks @Codeslinger, it works just right. I found when using it on sounds with attack and
decay that if I wanted the entire sound to fade away after x seconds that I should subtract the existing attack and decay values from the result of your formula. As long as the attack and delay aren’t longer than x.