Skip to content

Commit db0d61a

Browse files
author
dashodanger
committed
Add sf2flac encoder
1 parent 0cd22c3 commit db0d61a

File tree

6 files changed

+5009
-5
lines changed

6 files changed

+5009
-5
lines changed

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
.vscode
22
build
3-
tpsplayer.exe
4-
tpsplayer
3+
example/tpsplayer.exe
4+
example/tpsplayer
5+
sf2flac/sf2flac
6+
sf2flac/sf2flac.exe

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# tinyprimesynth
1+
# TinyPrimeSynth
22

33
## About
44
TinyPrimeSynth is an MIT-licensed, header-only C++11 MIDI soundfont synthesizer that attempts to abstract away many of the details of rendering MIDI files, providing an interface closer to that of a simple decoder. It is intended for use in game engines or other applications in which real-time playback without granular controls is sufficient. It is similar to the TinySoundFont project (https://github.com/schellingb/TinySoundFont), but provides a more robust implementation (for example, modulator support) at the expense of a somewhat higher overhead and the lack of signed 16-bit output.
@@ -24,13 +24,17 @@ It integrates the PrimeSynth (https://github.com/mosmeh/primesynth) and BW_Midi_
2424
## Compilation
2525
Define `TINYPRIMESYNTH_IMPLEMENTATION` before including `tinyprimesynth.hpp` in one source file within your project. It can then be included anywhere else that it needs to be referenced.
2626

27-
You may also define `TINYPRIMESYNTH_FLAC_SUPPORT` before `TINYPRIMESYNTH_IMPLEMENTAITON` to enable the internal FLAC decoder. This will allow for SF2FLAC (regular sf2 files which are FLAC-encoded) soundfont support. If you are already using a flac decoder in your program, you can leave this undefined and decode the SF2FLAC soundfont prior to loading into TinyPrimeSynth.
27+
You may also define `TINYPRIMESYNTH_FLAC_SUPPORT` before `TINYPRIMESYNTH_IMPLEMENTATION` to enable the internal FLAC decoder. This will allow for SF2FLAC (regular sf2 files which are FLAC-encoded) soundfont support. If you are already using a flac decoder in your program, you can leave this undefined and decode the SF2FLAC soundfont prior to loading into TinyPrimeSynth.
28+
29+
Any FLAC encoder may be used to create SF2FLAC files, but a simple encoder can be built with the files in the `sf2flac` directory. sf2flac treats the first argument passed to it as an sf2 file and attempts to encode it accordingly. If you are using your own encoder, it is recommended to treat the SF2 as a series of raw 16-bit signed samples.
30+
31+
Note that sf2flac uses the tflac library, which is under the BSD0 license. This does not affect TinyPrimeSynth when compiled on its own.
2832

2933
To compile a test program for Windows or Linux, please use the CMakeLists file in the `example` directory. It has a command-line interface whose usage can be shown with the 'help' parameter. You can pass either the bundled song and soundfont, or paths to your own.
3034

3135
If both the soundfont and song are successfully loaded, playback will begin. The song will loop indefinitely. Press enter or issue a break command to exit at any time.
3236

33-
Note that the test program uses the Sokol libraries, which are under the zlib license. It is bundled with `sf_GMbank.sf2` (encoded and renamed to `csound.sf2flac`), a public domain soundfont provided by the CSound project (https://github.com/csound/csound). It is also bundled with the track `ant_farm_melee.mid`, composed by Lee Jackson (https://dleejackson.lbjackson.com/) and used under the CC-BY-SA 4.0 license. None of these licenses affect tinyprimesynth when compiled on its own.
37+
Note that the test program uses the Sokol libraries, which are under the zlib license. It is bundled with `sf_GMbank.sf2` (encoded and renamed to `csound.sf2flac`), a public domain soundfont provided by the CSound project (https://github.com/csound/csound). It is also bundled with the track `ant_farm_melee.mid`, composed by Lee Jackson (https://dleejackson.lbjackson.com/) and used under the CC-BY-SA 4.0 license. None of these licenses affect TinyPrimeSynth when compiled on its own.
3438

3539
## Usage
3640
- Create a new instance of the Synthesizer class, passing to it your desired output rate.

sf2flac/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
##########################################
2+
# sf2flac
3+
##########################################
4+
cmake_minimum_required(VERSION 3.5)
5+
6+
project(
7+
sf2flac
8+
LANGUAGES C
9+
)
10+
11+
add_executable(
12+
sf2flac
13+
sf2flac.c
14+
)
15+
16+
add_custom_command( TARGET sf2flac POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:sf2flac>" ${CMAKE_SOURCE_DIR})

sf2flac/LICENSE

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Copyright (c) 2024 John Regan
2+
3+
Permission to use, copy, modify, and/or distribute this software for any
4+
purpose with or without fee is hereby granted.
5+
6+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
7+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
8+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
9+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
10+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
11+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
12+
PERFORMANCE OF THIS SOFTWARE.

sf2flac/sf2flac.c

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/* SPDX-License-Identifier: 0BSD */
2+
3+
// Adapted from the "Even Simpler Encoder" example with the following license:
4+
5+
/*
6+
Copyright (C) 2024 John Regan <[email protected]>
7+
8+
Permission to use, copy, modify, and/or distribute this software for any
9+
purpose with or without fee is hereby granted.
10+
11+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17+
PERFORMANCE OF THIS SOFTWARE.
18+
*/
19+
20+
#define TFLAC_DISABLE_COUNTERS
21+
#define TFLAC_IMPLEMENTATION
22+
#include "tflac.h"
23+
24+
#include <stdio.h>
25+
#include <stdlib.h>
26+
#include <string.h>
27+
#include <stdarg.h>
28+
29+
#define FRAME_SIZE 4096
30+
#define SAMPLERATE 44100
31+
#define BITDEPTH 16
32+
#define CHANNELS 1
33+
34+
static tflac_u16 unpack_u16le(const tflac_u8* d) {
35+
return (((tflac_u16)d[0]) ) |
36+
(((tflac_u16)d[1])<< 8);
37+
}
38+
39+
static tflac_s16 unpack_s16le(const tflac_u8* d) {
40+
return (tflac_s16)unpack_u16le(d);
41+
}
42+
43+
void
44+
repack_samples(tflac_s16 *s, tflac_u32 channels, tflac_u32 num) {
45+
tflac_u32 i = 0;
46+
while(i < (channels*num)) {
47+
s[i] = unpack_s16le( (tflac_u8*) (&s[i]) );
48+
i++;
49+
}
50+
}
51+
52+
typedef tflac_s16 sample;
53+
54+
int main(int argc, const char *argv[]) {
55+
tflac_u8 *buffer = NULL;
56+
tflac_u32 bufferlen = 0;
57+
tflac_u32 bufferused = 0;
58+
FILE *input = NULL;
59+
FILE *output = NULL;
60+
tflac_u32 frames = 0;
61+
sample *samples = NULL;
62+
void *tflac_mem = NULL;
63+
tflac t;
64+
65+
if(argc < 2) {
66+
printf("Usage: %s /path/to/sf2\n",argv[0]);
67+
return 1;
68+
}
69+
70+
if(tflac_size_memory(FRAME_SIZE) != TFLAC_SIZE_MEMORY(FRAME_SIZE)) {
71+
printf("Error with needed memory size: %u != %lu\n",
72+
tflac_size_memory(FRAME_SIZE),TFLAC_SIZE_MEMORY(FRAME_SIZE));
73+
return 1;
74+
}
75+
76+
if(tflac_size_frame(FRAME_SIZE,CHANNELS,BITDEPTH) != TFLAC_SIZE_FRAME(FRAME_SIZE,CHANNELS,BITDEPTH)) {
77+
printf("Error with needed frame size: %u != %lu\n",
78+
tflac_size_frame(FRAME_SIZE,CHANNELS,BITDEPTH),TFLAC_SIZE_FRAME(FRAME_SIZE,CHANNELS,BITDEPTH));
79+
return 1;
80+
}
81+
82+
tflac_detect_cpu();
83+
tflac_init(&t);
84+
85+
t.samplerate = SAMPLERATE;
86+
t.channels = CHANNELS;
87+
t.bitdepth = BITDEPTH;
88+
t.blocksize = FRAME_SIZE;
89+
t.enable_md5 = 0;
90+
91+
if (argv[1] == NULL) return 1;
92+
93+
input = fopen(argv[1],"rb");
94+
95+
if(input == NULL) return 1;
96+
97+
// At least check for the RIFF header
98+
char riff_check[4];
99+
100+
if (fread(&riff_check, 4, 1, input) != 1)
101+
{
102+
printf("Unable to read header!\n");
103+
fclose(input);
104+
return 1;
105+
}
106+
if (memcmp(riff_check, "RIFF", 4) != 0)
107+
{
108+
printf("Header invalid! (Is this an SF2 file?)\n");
109+
fclose(input);
110+
return 1;
111+
}
112+
113+
fseek(input, 0, SEEK_SET);
114+
115+
// Just append "flac" to whatever extension it had (probably sf2)
116+
size_t arg_length = strlen(argv[1]);
117+
char *out_name = (char *)calloc(arg_length + 5, sizeof(char));
118+
if (out_name == NULL) return 1;
119+
strncpy(out_name, argv[1], arg_length);
120+
out_name[arg_length] = 'f';
121+
out_name[arg_length+1] = 'l';
122+
out_name[arg_length+2] = 'a';
123+
out_name[arg_length+3] = 'c';
124+
125+
output = fopen(out_name,"wb");
126+
if(output == NULL) {
127+
printf("Unable to open output file %s!\n", out_name);
128+
free(out_name);
129+
fclose(input);
130+
return 1;
131+
}
132+
else
133+
free(out_name);
134+
135+
tflac_mem = malloc(tflac_size_memory(t.blocksize));
136+
if(tflac_mem == NULL) abort();
137+
138+
tflac_set_constant_subframe(&t, 1);
139+
tflac_set_fixed_subframe(&t, 1);
140+
141+
if(tflac_validate(&t, tflac_mem, tflac_size_memory(t.blocksize)) != 0) abort();
142+
143+
bufferlen = tflac_size_frame(FRAME_SIZE,CHANNELS,BITDEPTH);
144+
buffer = malloc(bufferlen);
145+
if(buffer == NULL) abort();
146+
147+
samples = (sample *)malloc(sizeof(sample) * CHANNELS * FRAME_SIZE);
148+
if(!samples) abort();
149+
150+
fwrite("fLaC",1,4,output);
151+
152+
tflac_encode_streaminfo(&t, 1, buffer, bufferlen, &bufferused);
153+
fwrite(buffer,1,bufferused,output);
154+
155+
while((frames = fread(samples,sizeof(sample) * CHANNELS, FRAME_SIZE, input)) > 0) {
156+
repack_samples(samples, CHANNELS, frames);
157+
158+
if(tflac_encode_s16i(&t, frames, samples, buffer, bufferlen, &bufferused) != 0) abort();
159+
fwrite(buffer,1,bufferused,output);
160+
}
161+
162+
tflac_finalize(&t);
163+
164+
fseek(output,4,SEEK_SET);
165+
tflac_encode_streaminfo(&t, 1, buffer, bufferlen, &bufferused);
166+
fwrite(buffer,1,bufferused,output);
167+
168+
fclose(input);
169+
fclose(output);
170+
free(tflac_mem);
171+
free(samples);
172+
free(buffer);
173+
174+
return 0;
175+
}
176+

0 commit comments

Comments
 (0)