Feed aggregator

Rate my resampling algorithm!

Renoise Forum - September 1, 2021 - 05:50

Hi everyone,

I’m Mark, casual Renoise user and one-time mod musician in the mid to late 1990’s (I went by the handle Arcturus - I was, at best, a footnote in the demoscene, and I’d be surprised if anybody remembered me - also there may have been more than one Arcturus). I’m now a professional software developer and I was feeling nostalgic, so I decided to try writing a mod player. Specifically, a player for 4-channel ProTracker mods - mostly just to see if I could do it. I decided to implement it in Kotlin running on the JVM, since that’s what I use a lot in my day job.

My goal is to get the player to play Space Debris correctly.

As you may be aware, the resampling algorithm is at the heart of any mod player. I was curious if my implementation is any good - it sounds fine so far, but I’m sure there’s probably a better way to do it. I’m posting this here to solicit feedback: What do you think of my implementation? Is there a better way to do it? Are there any obvious pitfalls? I don’t have a background in audio programming, but I know enough to get this far.

Anyway, here’s the repo: GitHub - mabersold/kotlin-protracker-demo: A JVM-based kotlin application that loads an Amiga Prot

Direct link to the resampler code: kotlin-protracker-demo/ChannelAudioGenerator.kt at main · mabersold/kotlin-protracker-demo · GitHub

Summary of the code organization:

model package contains the data classes to hold information about the song.
player package contains the class that actually sends the audio to the output device.
pcm package contains the classes that convert the song into a pcm audio stream. This is where the resampler lives.

The basic idea is that there is a single AudioGenerator class that keeps track of the position in the song. It has four ChannelAudioGenerator classes - one for each channel - that produces the audio in that channel. As each row passes, the main AudioGenerator sends data to the ChannelAudioGenerators (note commands and non-resampled audio data, basically) and the ChannelAudioGenerators send pcm data back to the main AudioGenerator, which mixes them and sends them back to the main class (which sends the data to the output device).

Summary of my algorithm:

The ChannelAudioGenerator has the following information: the period (basically the pitch that we need to resample to), an instrument number, and the audio data of the instrument.

The basic idea behind the algorithm is to do linear interpolation between each value of the original audio file to get the correct pitch. I’m operating under the assumption that I will not need to increase the frequency beyond what the original audio files already have (which seems to hold true so far from my testing) - I’ll only be reducing the frequency, and thus needing to interpolate data between the values of the existing audio data.

Calculate samples per second

It performs the following calculations. First, it takes the given period and calculates samples per second with the following formula:

samplesPerSecond = 7093789.2 / (period * 2)

7093789.2 is the PAL clock rate, in case you were curious. The period comes directly from the pattern data in the Protracker mod. For example, 428 represents a C-2 note, so that would be calculated at 8287.1369 samples per second.

Find out how many bytes we need to interpolate

So now I have samplesPerSecond. Next, I need to calculate how many bytes I need to interpolate. To do this, I basically find out how many times samplesPerSecond fits into our sampling rate of 44100. The functions for this are as follows:

private fun getIterationsUntilNextSample(samplesPerSecond: Double, counter: Double): Int { val iterationsPerSample = floor(SAMPLING_RATE / samplesPerSecond).toInt() val maximumCounterValueForExtraIteration = (SAMPLING_RATE - (samplesPerSecond * iterationsPerSample)) return iterationsPerSample + additionalIteration(counter, maximumCounterValueForExtraIteration) } private fun additionalIteration(counter: Double, maximumCounterValueForExtraIteration: Double): Int = if (counter < maximumCounterValueForExtraIteration) 1 else 0

“counter” is a double, starting at 0.0, that we continually add the samplesPerSecond to as we resample. When it exceeds 44100, we subtract 44100 from it, and then switch to the next pair of bytes in the original audio data to interpolate. So, the number of times we can multiply that counter until it exceeds 44100 is the number of bytes we need to interpolate between the first and the second byte (well, including the first byte, which we technically don’t interpolate).

For a C-2, if we start at the first two bytes of audio data from the instrument, these functions would conclude that there are six “iterations” before we switch to the next pair of bytes. So, we would to start with the first byte value, and then interpolate five times before we reach the second byte. The number of these iterations will not be uniform across the audio data: sometimes it will be five, sometimes six for a C-2.

So if, for example, the first two bytes were 6 and 18, we would need to interpolate five bytes in between those two values. It knows this because if we multiply 8287.1369 by five, it’s still under our sampling rate - we would need to multiply it by six before we exceed it.

Doing the actual interpolation

Finally, now that we know how many interpolations we need, we calculate the difference between the two bytes, calculate a slop, and then interpolate the bytes between them using a simple linear function. In practice it ends up looking like this:

(slope * currentIteration) + firstByte

So going back to the example of bytes with values 6 and 18, it would calculate a slope of 2, so we would end up with interpolated values of 6, 8, 10, 12, 14, 16 before the counter exceeds 441000 and then we move on to the next pair of bytes (18 and whatever’s after it).

A few notes:

  • To reduce confusion, I only use the word “sample” to refer to the individual bytes in a PCM audio stream or collection. I do not use “sample” to refer to the instruments, for that I either use “instrument” or “audio data.”
  • Effects aren’t implemented yet.
  • For now, I’ve kept all the audio data at 8-bit, which is why I’m just dealing with bytes and not increasing them to words/shorts
  • Part of my goal with this project is to make the code readable without too many bitwise operations, though some of that is unavoidable to some degree - I’m not going for performance
  • For now I’m just retrieving one byte at a time, but I may eventually switch to retrieving collections of bytes to reduce calculations.

1 post - 1 participant

Read full topic

Categories: Forum

Cymbal Choke/Poly Aftertouch

Renoise Forum - September 1, 2021 - 05:06

Using a Roland TD-20 with Superior Drummer. ALL works and records spot on, except I can not figure out how to get the cymbal choke/poly aftertouch to work. Cymbal choke works fine in Superior stand alone program, but once I load it in Renoise as VST I can no longer get the choke. I can see that renoise is recognizing the poly after touch though it does not choke the cymbal. On some occasions it will randomly choke and then never open back up. Am I missing a simple setting some where?

1 post - 1 participant

Read full topic

Categories: Forum

Observer when renoise has fully initialized

Renoise Forum - August 31, 2021 - 14:27

My tool throws an error because it’s trying to do something before Renoise has fully initialized. I know of

renoise.tool().tool_finished_loading_observable and renoise.too().app_new_document_observable but both of those fire too early when Renoise is loading.

Question: what callback or variable can I use to know that Renoise has fully loaded?

1 post - 1 participant

Read full topic

Categories: Forum

EQs with all pass filter

Renoise Forum - August 31, 2021 - 07:30

Hi,

I am looking for an EQ which also provides all pass filters, but like Disperser an allpass filter with variable resonance and strength. The bigger Melda EQs have an allpass band option, but it seems to be quite limited, there is no amount and it does not sound as effective as Disperser. Any idea?

1 post - 1 participant

Read full topic

Categories: Forum

How to remap arrow keys?

Renoise Forum - August 31, 2021 - 04:07

Is it possible to remap the arrow keys? I’m using a Ducky One 2 Mini keyboard, so no arrow keys. Whenever I try to map new keys I get a ‘Computer Keyboard Piano’ error message and I can’t find any way around it.

1 post - 1 participant

Read full topic

Categories: Forum

Hello there - track

Renoise Forum - August 30, 2021 - 20:09

Here is a new track. Hope you like it

2 posts - 2 participants

Read full topic

Categories: Forum

Capturing VST windows when recording/streaming in OBS

Renoise Forum - August 29, 2021 - 21:56

Hello,

How do I capture the VST windows when I’m recording/streaming Renoise in OBS? OBS doesn’t seem to capture the VST windows when I try to record them. I don’t want to use the dislay capture.

Any help?

1 post - 1 participant

Read full topic

Categories: Forum

Sample loop louder at start of next pattern?

Renoise Forum - August 29, 2021 - 19:41

Hi guys,

I’ve noticed that when I use a loop, even one that’s the exact same length of the pattern and with no loop on, it plays louder on the first beat on the next pattern when it is triggered again.

Has anyone else noticed this? If so, how do I stop Renoise doing this?

P.S… A huge thank you to the Renoise team for the best DAW on the planet.

1 post - 1 participant

Read full topic

Categories: Forum

Sick puppy - Death the leveller (Dark Ambient)

Renoise Forum - August 29, 2021 - 18:08

Have a listen

1 post - 1 participant

Read full topic

Categories: Forum

Music based on Afghani folk

Renoise Forum - August 29, 2021 - 01:20

My new hoise ipspired by latest news from Afghan

1 post - 1 participant

Read full topic

Categories: Forum

Trouble passing options to renoise.song():render()?

Renoise Forum - August 28, 2021 - 16:30

Hey all I’m not having luck passing the options array into renoise.song():render(). In the code below it renders but the file is always 32 bit and ignores the start/end positions which I think? I’m inputting like it wants.

function rendering_done_callback() renoise.app():show_message("done") end filename = "C:\\Users\\Public\\test.wav" star = renoise.SongPos(1,1) en = renoise.SongPos(2,1) options = { star, en, 44100, 16 , 'default', 'high', } renoise.song():render(options, filename, rendering_done_callback) --options = { -- start_pos, -- renoise.SongPos object. by default the song start. -- end_pos, -- renoise.SongPos object. by default the song end. -- sample_rate, -- one of 22050, 44100, 48000, 88200, 96000, 192000. \ -- -- by default the players current rate. -- bit_depth , -- number, one of 16, 24 or 32. by default 32. -- interpolation, -- string, one of 'default', 'precise'. by default default'. -- priority, -- string, one "low", "realtime", "high". \ -- -- by default "high". -- }

3 posts - 3 participants

Read full topic

Categories: Forum

Live stream revisiting a tune from 10 years ago?

Renoise Forum - August 28, 2021 - 13:23

Hi
Following the Renoise Q+A we streamed earlier this year I thought about possibly doing a stream where I give a 10 year old track a 2021 update - using the skills and knowledge I have since then.

It proved to be my most popular remix/track ever using Renoise…the band even played it on stage when they toured!

Does that appeal to anyone? It’s a fair bit to set up so just want to gauge interest…

Thanks
Chris [midierror]

1 post - 1 participant

Read full topic

Categories: Forum

Can I export a tempo map?

Renoise Forum - August 28, 2021 - 07:27

Hi, I have a tune I’m working on in Renoise which has a lot of tempo changes. I would like to do some work on it in Logic, is there a way to export a tempo map from Renoise that I can import to Logic?

Rewire isn’t an option I don’t think, because most annoyingly, Logic will only be the master these days (found that change really annoying).

A last ditch option might be to export an audio click track from Renoise and try using the smart tempo function Logic, but I’ve never used it before.

1 post - 1 participant

Read full topic

Categories: Forum

Aquilyzer - Bounce (Hands Up Mix) | Dance & EDM

Renoise Forum - August 27, 2021 - 21:58

New hands up song done in Renoise. Hope you like it.

Also on youtube:

1 post - 1 participant

Read full topic

Categories: Forum

Q key is now B3 instead of C4, how to shift it back?

Renoise Forum - August 27, 2021 - 20:53

sorry if this is super easy but my kick drum in one of my projects somehow got shifted a note off and now my usual method of inserting kicks manually with Q key is disabled. i figured out how to move the octave range up and down but i can’t seem to center it back to where Q = C4. thanks for anyone who can help.

1 post - 1 participant

Read full topic

Categories: Forum

Usb mic recommendations

Renoise Forum - August 27, 2021 - 18:26

Hi. I was wondering if anyone uses a USB mic to capture audio in the sample recorder function. I have a Beyerdynamics fox but renoise says it can’t use it cuz it’s a mono capture. I’m guessing a stereo USB mic of some kind would work but I’m curious if anyone else has had good results with a particular mic. Thanks.

1 post - 1 participant

Read full topic

Categories: Forum

How to chop multiple samples more or less exactly the same?

Renoise Forum - August 27, 2021 - 16:56

Hi, new user here. I have a multi-track drum beat I’d like to chop and rearrange as if it was a single track. It’s a drum break and 2 drum machines, with the drum machine instruments each on their own tracks. I could render it as a single track, but I’d like to save the mixing for later. Not everything is precisely quantized, otherwise I could chop everything along the grid.

Here’s what I’ve been doing so far: I rendered the break and the main kick/snare from one of the drum machines into a stereo track, with the break and kick/snare hard panned, then I collapsed it to mono (so it’s not annoying to listen to), and I chopped/rearranged that. This way I can separate it back out to 2 mono tracks for mixing later. Works pretty well. But now I have the tom and hat tracks to chop the same as the break/kick/snare track. I’m thinking if I chop them the same, then I could just copy the sequence to the new tracks, then assign the relevant samples to those tracks.

I think what would work is if I could view the time length of each chopped sample, then I could apply that each new sample. So for example, say the first chopped sample is 1.023 seconds long, then I could import a new sample, slice it at 1.023 seconds, and so on. But I’m not seeing that view.

Does it make sense what I’m trying to do? Any ideas? Thanks in advance.

1 post - 1 participant

Read full topic

Categories: Forum

Autoseek sample option by default

Renoise Forum - August 27, 2021 - 11:06

Hey!
I know this topic has been discussed several times on the forum, last time in 2011, @Conner_Bw saif that it was cpu intensive but now we are in 2021, our computers have so much power
When we import 40 stems, it s a pain in the ass to check the autoseek option for each and everyone of them! Please guys it’s time to do it!

1 post - 1 participant

Read full topic

Categories: Forum

Having issues with mkdir and also rendering to files

Renoise Forum - August 27, 2021 - 03:54

Hey guys,

I’m stuck on a couple very basic issues with scripting:

  1. I need to make a new folder, but os.mkdir(“C:\Users\Public\testFolder”) doesnt work.
    It’s returning nil…as far as I can tell this is what the documentation says to do

  2. I’m trying to use renoise.song(): render() but I keep getting the error: ‘invalid render filename parameter. expected a non empty path and name, a valid file_name within an existing path’

I couldn’t find any preexisting scripts that do these things as a reference so hopefully someone can set me straight

dirr = "C:\Users\Public\test" os.mkdir(dirr) function rendering_done_callback() renoise.app():show_message("done") end filename = "C:\Users\Public\otherTest\testRender.wav" options = { 0, 50, 44100, 16 , 'default', 'high', } renoise.song():render(options, filename, rendering_done_callback)

1 post - 1 participant

Read full topic

Categories: Forum

This is illegal you know, the first track from my upcoming album

Renoise Forum - August 27, 2021 - 01:18

Combining keygen-ish chiptune, electro, progressive house and complextro elements among other stuff, this track is meant to be the grand opener to a W.I.P. album of mine. Made entirely in Renoise 3.3.2.

1 post - 1 participant

Read full topic

Categories: Forum

Pages

Subscribe to Renoise aggregator