Looped SFZ instruments

Link to good samples/soundfonts at http://wiki.linuxaudio.org/wiki/free_audio_data

Moderators: MattKingUSA, khz

Post Reply
Lyberta
Established Member
Posts: 681
Joined: Sat Nov 01, 2014 8:15 pm
Location: The Internet

Looped SFZ instruments

Post by Lyberta »

So I'm trying to learn how to create looped SFZ instruments. Here it says that by default this info comes from the audio file. Does this mean it reads WAV chunks? What kind of editor can create those?

I've tried to put just loop_mode=loop_continuous but LinuxSampler refuses to loop this. Does this mean I need full info inside WAV or manually specify start and end in SFZ?

j_e_f_f_g
Established Member
Posts: 1060
Joined: Fri Aug 10, 2012 10:48 pm

Re: Looped SFZ instruments

Post by j_e_f_f_g »

loop_mode=continuous, without also specifying start and end, means that the looping info is embedded in your wave file.

Obviously that isn't the case.

So either include start and end...

... or get a good wave editor that allows you to set/save loop points (not Audacity -- it deletes them), with a (preferably 50%) crossfade algorithm (Waveosaur).

Lyberta
Established Member
Posts: 681
Joined: Sat Nov 01, 2014 8:15 pm
Location: The Internet

Re: Looped SFZ instruments

Post by Lyberta »

j_e_f_f_g wrote:... or get a good wave editor that allows you to set/save loop points (not Audacity -- it deletes them), with a (preferably 50%) crossfade algorithm (Waveosaur).
Wavosaur is Windows only.

j_e_f_f_g
Established Member
Posts: 1060
Joined: Fri Aug 10, 2012 10:48 pm

Re: Looped SFZ instruments

Post by j_e_f_f_g »

Lyberta wrote:Wavosaur is Windows only.
And it's also the single best tool for looping waves I've ever used. I'd advise you to try to get it running under Wine first, before wasting time on inferior tools.

User avatar
bhilmers
Established Member
Posts: 176
Joined: Mon Apr 23, 2012 11:44 pm

Re: Looped SFZ instruments

Post by bhilmers »

j_e_f_f_g wrote:
Lyberta wrote:Wavosaur is Windows only.
And it's also the single best tool for looping waves I've ever used. I'd advise you to try to get it running under Wine first, before wasting time on inferior tools.
Ditto. Wavosaur is the best free audio editor I've ever used and it's been running perfectly in WINE for several years. I do most of my editing with it. The project is on the verge of abandonware and I hope the authors release the source.

Lyberta
Established Member
Posts: 681
Joined: Sat Nov 01, 2014 8:15 pm
Location: The Internet

Re: Looped SFZ instruments

Post by Lyberta »

Ok, I'm not interesting in proprietary Windows software (ugh, double whammy). Can someone say what chunks are needed? I will plug them into my WAV reading/writing code.

antiesen
Established Member
Posts: 153
Joined: Sat Aug 27, 2011 3:36 pm

Re: Looped SFZ instruments

Post by antiesen »

I welcome the attitude not to use system-foreign programs To your first question:
Maybe a little overdone, but i would recommend polyphone. You can loop inside the programm and export wav, sf2 and sfz.
This was made to end all partys - Einstürzende Neubauten 1985

nilshi
Established Member
Posts: 301
Joined: Wed Oct 22, 2008 9:05 pm
Contact:

Re: Looped SFZ instruments

Post by nilshi »

Do not use the .wav built-in loop format. It is very fragile and practically not supported.
Always use external meta data, in this case sfz loop.

Lyberta
Established Member
Posts: 681
Joined: Sat Nov 01, 2014 8:15 pm
Location: The Internet

Re: Looped SFZ instruments

Post by Lyberta »

antiesen wrote:I welcome the attitude not to use system-foreign programs To your first question:
Maybe a little overdone, but i would recommend polyphone. You can loop inside the programm and export wav, sf2 and sfz.
Argh, it had Debian Stable build but it is too old for my Debian Testing.
falkTX wrote:This part of SFZero references Loop0Start and Loop0End.
https://github.com/altalogix/SFZeroModu ... le.cpp#L27
Ok, it uses JUCE to read the chucks. That's fine but at this moment I'm not very interested in reading through JUCE (there are also license problems, right?).
nilshi wrote:Do not use the .wav built-in loop format. It is very fragile and practically not supported.
Always use external meta data, in this case sfz loop.
Sure but I need to support those to fully support the SFZ format.



Ok, here's a single period of 440Hz sine wave, just 99 frames. Can someone put loop start at frame 0 and loop end at frame 99? Then I will figure out how to read this file with hex editor and C++.
You do not have the required permissions to view the files attached to this post.

antiesen
Established Member
Posts: 153
Joined: Sat Aug 27, 2011 3:36 pm

Re: Looped SFZ instruments

Post by antiesen »

So here are the examples. I have set the loop end to 99, but should actually be at 100 to loop the whole thing.
Then another one with differet loop-settings an one with changed root-key and detuning.
You do not have the required permissions to view the files attached to this post.
This was made to end all partys - Einstürzende Neubauten 1985

j_e_f_f_g
Established Member
Posts: 1060
Joined: Fri Aug 10, 2012 10:48 pm

Re: Looped SFZ instruments

Post by j_e_f_f_g »

Let me save you some time:

Code: Select all

#ifndef O_NOATIME
#define O_NOATIME        01000000
#endif

#pragma pack(1)
/////////////////////// WAVE File Stuff /////////////////////
// An IFF file header looks like this
typedef struct
{
	unsigned char	ID[4];	// could be {'R', 'I', 'F', 'F'} or {'F', 'O', 'R', 'M'}
	uint32_t			Length;	// Length of subsequent file (including remainder of header). This is in
							// Intel reverse byte order if RIFF, Motorola format if FORM.
	unsigned char	Type[4];	// {'W', 'A', 'V', 'E'} or {'A', 'I', 'F', 'F'}
} FILE_head;

// An IFF chunk header looks like this
typedef struct
{
	unsigned char	ID[4];	// 4 ascii chars that is the chunk ID
	uint32_t			Length;	// Length of subsequent data within this chunk. This is in Intel reverse byte
							// order if RIFF, Motorola format if FORM. Note: this doesn't include any
							// extra byte needed to pad the chunk out to an even size.
} CHUNK_head;

// WAVE fmt chunk
typedef struct {
	short			wFormatTag;
	unsigned short	wChannels;
	uint32_t			dwSamplesPerSec;
	uint32_t			dwAvgBytesPerSec;
	unsigned short	wBlockAlign;
	unsigned short	wBitsPerSample;
  // Note: there may be additional fields here, depending upon wFormatTag
} FORMAT;

typedef struct {
	FILE_head		head;
	CHUNK_head		fmt;
	FORMAT			fmtdata;
} WAVESTART;

// WAVE Sample Loop struct
typedef struct
{
	int32_t			dwIdentifier;
	int32_t			dwType;
	uint32_t			dwStart;
	uint32_t			dwEnd;
	int32_t			dwFraction;
	int32_t			dwPlayCount;
} SAMPLELOOP;

// WAVE Smpl chunk
typedef struct
{
	int32_t	dwManufacturer;
	int32_t	dwProduct;
	int32_t	dwSamplePeriod;
	int32_t	dwMIDIUnityNote;
	int32_t	dwMIDIPitchFraction;
	int32_t	dwSMPTEFormat;
	int32_t	dwSMPTEOffset;
	int32_t	cSampleLoops;
	int32_t	cbSamplerData;
} SAMPLER;

typedef struct {
  int32_t			dwIdentifier;
  uint32_t			dwPosition;
  unsigned char	fccChunk[4];
  uint32_t			dwChunkStart;
  uint32_t			dwBlockStart;
  uint32_t			dwSampleOffset;
} CUELOOP;
#pragma pack()

static const unsigned char Cue[4] = { 'c', 'u', 'e', ' ' };
static const unsigned char Smpl[4] = { 's', 'm', 'p', 'l' };
static const unsigned char Data[4] = { 'd', 'a', 't', 'a' };

/********************** compareID() *********************
 * Compares the passed ID str (ie, a ptr to 4 Ascii
 * bytes) with the ID at the passed ptr. Returns TRUE if
 * a match, FALSE if not.
 */

static unsigned char compareID(const unsigned char * id, unsigned char * ptr)
{
	register unsigned char i = 4;

	while (i--)
	{
		if ( *(id)++ != *(ptr)++ ) return 0;
	}
	return 1;
}

typedef struct {
	uint32_t	DataLength;
	uint32_t	LoopStart;
	uint32_t	LoopEnd;
} MY_WAVE_INFO;

uint32_t readWave(const char * fn)
{
	WAVESTART				file;
	FILE_head				head;
	MY_WAVE_INFO			myInfo;
	uint32_t					action;
	register int			inHandle;

	// Open the WAVE
	inHandle = open(fn, O_RDONLY|O_NOATIME);

	// Read in IFF File header and fmt chunk
	read(inHandle, &file, sizeof(WAVESTART));

	// Assume no loop
	myInfo.LoopStart = myInfo.LoopEnd = (uint32_t)-1;

	myInfo.DataLength = 0;

	// Skip any extra fmt bytes
	head.Length = file.fmt.Length - sizeof(FORMAT);
	goto skip;

	while (read(inHandle, &head, sizeof(CHUNK_head)) == sizeof(CHUNK_head))
	{
		// ============================ Is it a data chunk? ===============================
		if (compareID(&Data[0], &head.ID[0]))
		{
			read(inHandle, wavePtr, head.Length);
			myInfo.DataLength = head.Length;
		}

		// ============================ Is it an smpl chunk? ===============================
		else if (compareID(&Smpl[0], &head.ID[0]))
		{
			SAMPLER					smpl;
			SAMPLELOOP				smploop;

			// Read in SAMPLER
			read(inHandle, &smpl, sizeof(SAMPLER));
			head.Length -= sizeof(SAMPLER);

			// Use only the first loop
			if (smpl.cSampleLoops)
			{
				// Read in sample loop
				read(inHandle, &smploop, sizeof(SAMPLELOOP));

				myInfo.LoopStart = smploop.dwStart;
				myInfo.LoopEnd = smploop.dwEnd;

				head.Length -= sizeof(SAMPLELOOP);
			}

			// Skip remainder of chunk
			goto skip;
		}

		// ============================ Is it a cue chunk? ===============================
		else if (compareID(&Cue[0], &head.ID[0]))
		{
			// Give preference to the smpl chunk if we found one
			if (myInfo.LoopStart == (uint32_t)-1)
			{
				CUELOOP			cueloop;

				// Read in dwCuePoints
				read(inHandle, &action, sizeof(uint32_t));
				head.Length -= sizeof(uint32_t);

				// Use only the first loop
				if (action)
				{
					// Read in cue loop
					read(inHandle, &cueloop, sizeof(CUELOOP));

					myInfo.LoopStart = cueloop.dwPosition * (file.fmtdata.wBitsPerSample == 16 ? 2 : 4);	// note: We don't care about 8-bit files
					myInfo.LoopEnd = myInfo.DataLength / file.fmtdata.wChannels;

					head.Length -= sizeof(CUELOOP);
				}
			}

			// Skip remainder of chunk
			goto skip;
		}

		// ============================ Skip this chunk ===============================
		else
		{
skip:		if (head.Length & 1) ++head.Length;  // If odd, round it up to account for pad byte
			lseek(inHandle, head.Length, SEEK_CUR);
		}
	}

	// We got the data? Note: size in bytes
	if (myInfo.DataLength && myInfo.LoopStart != (uint32_t)-1)
	{
		// Offsets in bytes
		myInfo.LoopStart *= ((file.fmtdata.wBitsPerSample == 16 ? 2 : 4) * file.fmtdata.wChannels);
		myInfo.LoopEnd *= ((file.fmtdata.wBitsPerSample == 16 ? 2 : 4) * file.fmtdata.wChannels);
		if (myInfo.LoopStart != myInfo.LoopEnd && myInfo.LoopStart < myInfo.DataLength)
		{
			if (myInfo.LoopEnd > myInfo.DataLength) myInfo.LoopEnd = myInfo.DataLength;
			if (myInfo.LoopStart > myInfo.LoopEnd)
			{
				register uint32_t	temp;

				temp = myInfo.LoopEnd;
				myInfo.LoopEnd = myInfo.LoopStart;
				myInfo.LoopStart = temp;
			}
		}
	}

	close(inHandle);
}

j_e_f_f_g
Established Member
Posts: 1060
Joined: Fri Aug 10, 2012 10:48 pm

Re: Looped SFZ instruments

Post by j_e_f_f_g »

Note: I stripped out the error checking to make it more readable. Do not use in production code without all the error checks on open() and read() and lseek(), and checking chunk size against file size for corruption.

j_e_f_f_g
Established Member
Posts: 1060
Joined: Fri Aug 10, 2012 10:48 pm

Re: Looped SFZ instruments

Post by j_e_f_f_g »

falkTX wrote:We cant just borrow code like that... what license is that in?
Where did it came from, did you wrote it yourself or copied from somewhere else?
I wrote it. It originally came from a Windows utility i wrote/released two decades ago called Aiff2Wav (convert between WAVE and apple AIFF formats), and has been subsequently used in many of my other projects, most recently BackupBand's ConvertWave utility. The latter is GPL. (Considering BSD going forward, because the "amateur gpl licensing police" have become more of an actual, real-world annoyance than anyone who has ever "borrowed" my code. Seriously.) But the above snippet is so utterly basic, simple, short, and limited in scope that i consider it public domain. It would be the epitome of hubris for a developer to think the above snippet should merit copyright. And i ain't one of those a-holes.

If someone wants it, use it.

Post Reply