Compare commits
4 Commits
1575b3053a
...
c1558d99ee
| Author | SHA1 | Date | |
|---|---|---|---|
| c1558d99ee | |||
| 8203e1f2f4 | |||
| ac6b6138d4 | |||
| ae0b7526a5 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -7,4 +7,8 @@ tool/pack_file
|
||||
*.zip
|
||||
*.tar
|
||||
*.pyc
|
||||
*.dds
|
||||
*.dds
|
||||
*.zlib
|
||||
tools/compress
|
||||
tools/opus_encode
|
||||
*.pcm
|
||||
38
Makefile
38
Makefile
@ -23,6 +23,7 @@ CFLAGS += -fno-strict-aliasing
|
||||
CFLAGS += -I./include
|
||||
CFLAGS += -I./data
|
||||
CFLAGS += -I../SDL3-dist/include
|
||||
CFLAGS += -I../opus-dist/include
|
||||
CFLAGS += -fpic
|
||||
CFLAGS += -ffunction-sections
|
||||
CFLAGS += -fdata-sections
|
||||
@ -63,7 +64,22 @@ OBJS = \
|
||||
src/renpy/vulkan.o \
|
||||
src/renpy/script.o \
|
||||
src/renpy/interpreter.o \
|
||||
src/renpy/interact.o
|
||||
src/renpy/interact.o \
|
||||
src/audio.o
|
||||
|
||||
ifeq ($(UNAME),Linux)
|
||||
ZLIB = ../zlib-1.3.2
|
||||
CFLAGS += -I$(ZLIB)
|
||||
OBJS += \
|
||||
$(ZLIB)/uncompr.o \
|
||||
$(ZLIB)/inflate.o \
|
||||
$(ZLIB)/inffast.o \
|
||||
$(ZLIB)/inftrees.o \
|
||||
$(ZLIB)/trees.o \
|
||||
$(ZLIB)/zutil.o \
|
||||
$(ZLIB)/crc32.o \
|
||||
$(ZLIB)/adler32.o
|
||||
endif
|
||||
|
||||
WORLDS = \
|
||||
data/minecraft/midnightmeadow/inthash.o \
|
||||
@ -78,7 +94,8 @@ LIBS = \
|
||||
../SDL3-dist/lib/libSDL3.a
|
||||
else
|
||||
LIBS = \
|
||||
../SDL3-dist/lib64/libSDL3.a
|
||||
../SDL3-dist/lib64/libSDL3.a \
|
||||
../opus-dist/lib/libopus.a
|
||||
endif
|
||||
|
||||
all: main
|
||||
@ -95,6 +112,18 @@ all: main
|
||||
#%.dds: %.png
|
||||
# WINEDEBUG=-all wine $(HOME)/Texconv.exe -y -nogpu -nowic -dx10 --format BC7_UNORM_SRGB -m 1 $< -o $(dir $@)
|
||||
|
||||
%.pcm: %.wav
|
||||
ffmpeg -loglevel quiet -y -i $< -c:a pcm_s16le -ar 48000 -ac 2 -f s16le $@
|
||||
|
||||
%.pcm: %.ogg
|
||||
ffmpeg -loglevel quiet -y -i $< -c:a pcm_s16le -ar 48000 -ac 2 -f s16le $@
|
||||
|
||||
%.pcm: %.mp3
|
||||
ffmpeg -loglevel quiet -y -i $< -c:a pcm_s16le -ar 48000 -ac 2 -f s16le $@
|
||||
|
||||
%.opus.bin: %.pcm
|
||||
./tools/opus_encode $< $@
|
||||
|
||||
main: $(OBJS) $(LIBS) $(SCENES) $(WORLDS)
|
||||
$(CC) $(ARCH) $(LDFLAGS) $(FLAGS) $(OPT) $(DEBUG) $^ -o $@
|
||||
|
||||
@ -104,7 +133,10 @@ main: $(OBJS) $(LIBS) $(SCENES) $(WORLDS)
|
||||
tool/pack_file: tool/pack_file.cpp
|
||||
make -C tool pack_file
|
||||
|
||||
src/pack.o: files.pack
|
||||
%.pack.zlib: %.pack
|
||||
./tools/compress $< $@
|
||||
|
||||
src/pack.o: files.pack.zlib
|
||||
|
||||
PACK_FILENAMES = $(shell cat filenames.txt)
|
||||
files.pack: tool/pack_file $(PACK_FILENAMES) filenames.txt
|
||||
|
||||
BIN
audio/music/MistAmbience.wav
Normal file
BIN
audio/music/MistAmbience.wav
Normal file
Binary file not shown.
BIN
audio/music/PhrygianButterflies.opus.bin
Normal file
BIN
audio/music/PhrygianButterflies.opus.bin
Normal file
Binary file not shown.
BIN
audio/music/PhrygianButterflies.wav
Normal file
BIN
audio/music/PhrygianButterflies.wav
Normal file
Binary file not shown.
BIN
audio/music/Poem1.ogg
Normal file
BIN
audio/music/Poem1.ogg
Normal file
Binary file not shown.
BIN
audio/music/Poem1.opus.bin
Normal file
BIN
audio/music/Poem1.opus.bin
Normal file
Binary file not shown.
BIN
audio/music/ScaredMice.opus.bin
Normal file
BIN
audio/music/ScaredMice.opus.bin
Normal file
Binary file not shown.
BIN
audio/music/ScaredMice.wav
Normal file
BIN
audio/music/ScaredMice.wav
Normal file
Binary file not shown.
BIN
audio/music/TinyForestMinstrels.opus.bin
Normal file
BIN
audio/music/TinyForestMinstrels.opus.bin
Normal file
Binary file not shown.
BIN
audio/music/TinyForestMinstrels.wav
Normal file
BIN
audio/music/TinyForestMinstrels.wav
Normal file
Binary file not shown.
BIN
audio/music/WheatFields.opus.bin
Normal file
BIN
audio/music/WheatFields.opus.bin
Normal file
Binary file not shown.
BIN
audio/music/WheatFields.wav
Normal file
BIN
audio/music/WheatFields.wav
Normal file
Binary file not shown.
BIN
audio/placeholdermeow.mp3
Normal file
BIN
audio/placeholdermeow.mp3
Normal file
Binary file not shown.
BIN
audio/placeholdermeow.opus.bin
Normal file
BIN
audio/placeholdermeow.opus.bin
Normal file
Binary file not shown.
BIN
audio/sfx/Chime.ogg
Normal file
BIN
audio/sfx/Chime.ogg
Normal file
Binary file not shown.
BIN
audio/sfx/Chime.opus.bin
Normal file
BIN
audio/sfx/Chime.opus.bin
Normal file
Binary file not shown.
BIN
audio/sfx/MistAmbience.ogg
Normal file
BIN
audio/sfx/MistAmbience.ogg
Normal file
Binary file not shown.
BIN
audio/sfx/MistAmbience.opus.bin
Normal file
BIN
audio/sfx/MistAmbience.opus.bin
Normal file
Binary file not shown.
@ -76,6 +76,7 @@ label start:
|
||||
#voice "n4test.mp3"
|
||||
play TinyForestMinstrels "music/TinyForestMinstrels.ogg"
|
||||
n "Tiny minstrels can be heard amongst the trees"
|
||||
stop TinyForestMinstrels fadeout 5.5
|
||||
|
||||
|
||||
scene bgforest1
|
||||
@ -127,7 +128,7 @@ label start:
|
||||
with Dissolve(1.0)
|
||||
|
||||
|
||||
voice "n5test.ogg"
|
||||
#voice "n5test.ogg"
|
||||
n "As the minstrel mice girls continue along the path, the forest opens up into a beautiful field of flowers"
|
||||
|
||||
play PhrygianButterflies "music/PhrygianButterflies.ogg"
|
||||
|
||||
@ -11,3 +11,12 @@ data/renpy/images/ch/cat.dds
|
||||
data/renpy/images/ch/catw.dds
|
||||
data/renpy/images/ch/Eily.dds
|
||||
data/renpy/images/ch/Alice.dds
|
||||
|
||||
audio/sfx/Chime.opus.bin
|
||||
audio/sfx/MistAmbience.opus.bin
|
||||
audio/music/TinyForestMinstrels.opus.bin
|
||||
audio/music/PhrygianButterflies.opus.bin
|
||||
audio/music/Poem1.opus.bin
|
||||
audio/placeholdermeow.opus.bin
|
||||
audio/music/ScaredMice.opus.bin
|
||||
audio/music/WheatFields.opus.bin
|
||||
|
||||
11
include/audio.h
Normal file
11
include/audio.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "renpy/language.h"
|
||||
|
||||
namespace audio {
|
||||
void init();
|
||||
void load(renpy::language::audio const * const audio, int count);
|
||||
void update();
|
||||
void play(int audio_index);
|
||||
void stop(int audio_index, double fadeout);
|
||||
}
|
||||
@ -3,6 +3,8 @@
|
||||
#include <stdint.h>
|
||||
|
||||
namespace file {
|
||||
void init();
|
||||
|
||||
void const * open(char const * filename, uint32_t * out_size);
|
||||
|
||||
void * openRelative(char const * filename, uint32_t * out_size);
|
||||
|
||||
@ -25,6 +25,7 @@ namespace renpy::language {
|
||||
|
||||
struct audio {
|
||||
char const * const path;
|
||||
double loop_end;
|
||||
};
|
||||
|
||||
struct image {
|
||||
@ -64,6 +65,11 @@ namespace renpy::language {
|
||||
uint32_t audioIndex;
|
||||
};
|
||||
|
||||
struct stop {
|
||||
uint32_t audioIndex;
|
||||
double fadeout;
|
||||
};
|
||||
|
||||
struct _return {
|
||||
};
|
||||
|
||||
@ -92,10 +98,6 @@ namespace renpy::language {
|
||||
struct with {
|
||||
};
|
||||
|
||||
struct stop {
|
||||
uint32_t channelIndex;
|
||||
};
|
||||
|
||||
struct pause {
|
||||
float duration;
|
||||
};
|
||||
|
||||
@ -56,7 +56,6 @@ class Play:
|
||||
channel: lex.Token
|
||||
path: lex.Token
|
||||
fadeout: lex.Token
|
||||
noloop: bool
|
||||
|
||||
__repr__ = lexeme_repr
|
||||
|
||||
@ -263,16 +262,15 @@ def parse_play(tokens, index):
|
||||
if fadeout.type != TT.NUMBER:
|
||||
raise ParseException("expected number", fadeout)
|
||||
index += 2
|
||||
noloop = False
|
||||
#noloop = False
|
||||
if token.type == TT.NOLOOP:
|
||||
noloop = True
|
||||
#noloop = True
|
||||
index += 1
|
||||
|
||||
play = Play(
|
||||
channel = channel,
|
||||
path = path,
|
||||
fadeout = fadeout,
|
||||
noloop = noloop,
|
||||
)
|
||||
return index, play
|
||||
|
||||
@ -480,18 +478,19 @@ def parse_stop(tokens, index):
|
||||
if channel.type != TT.IDENTIFIER:
|
||||
raise ParseException("expected identifier", channel)
|
||||
|
||||
index += 1
|
||||
token = tokens[index]
|
||||
fadeout = None
|
||||
if token.type == TT.FADEOUT:
|
||||
fadeout = tokens[index + 1]
|
||||
if fadeout.type != TT.NUMBER:
|
||||
raise ParseException("expected number", fadeout)
|
||||
index += 2
|
||||
fadeout = tokens[index + 1]
|
||||
if fadeout.type != TT.FADEOUT:
|
||||
raise ParseException("expected fadeout", channel)
|
||||
|
||||
number = tokens[index + 2]
|
||||
if number.type != TT.NUMBER:
|
||||
raise ParseException("expected number", number)
|
||||
|
||||
index += 3
|
||||
|
||||
stop = Stop(
|
||||
channel = channel,
|
||||
fadeout = fadeout
|
||||
fadeout = number
|
||||
)
|
||||
return index, stop
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ class State:
|
||||
characters_lookup: dict[str, int] # identifier to character index
|
||||
labels_lookup: dict[str, int] # identifier to statement index
|
||||
audio_lookup: dict[str, int]
|
||||
channel_lookup: dict[str, int]
|
||||
string_lookup: dict[str, int]
|
||||
|
||||
global_identifiers: set[str]
|
||||
@ -51,6 +52,14 @@ simple_statement_types = {
|
||||
parse.Hide,
|
||||
}
|
||||
|
||||
loops = {
|
||||
"ScaredMice": 8.0,
|
||||
"PhrygianButterflies": 40.125,
|
||||
"MistAmbience": 22.0,
|
||||
"TinyForestMinstrels": 44.0,
|
||||
"WheatFields": 34.0,
|
||||
}
|
||||
|
||||
def pass1(state, ast):
|
||||
if type(ast) is parse.Image:
|
||||
key = lhs_key(ast.name)
|
||||
@ -75,7 +84,12 @@ def pass1(state, ast):
|
||||
key = lhs_key(ast.name)
|
||||
assert key not in state.labels_lookup
|
||||
state.labels_lookup[key] = len(state.statements)
|
||||
elif type(ast) in {parse.Play, parse.Voice}:
|
||||
elif type(ast) is parse.Play:
|
||||
if ast.path.lexeme not in state.audio_lookup:
|
||||
index = len(state.audio_lookup)
|
||||
state.audio_lookup[ast.path.lexeme] = index
|
||||
state.channel_lookup[ast.channel.lexeme] = index
|
||||
elif type(ast) is parse.Voice:
|
||||
if ast.path.lexeme not in state.audio_lookup:
|
||||
index = len(state.audio_lookup)
|
||||
state.audio_lookup[ast.path.lexeme] = index
|
||||
@ -135,7 +149,7 @@ def pass2_statement(state, pc, statement):
|
||||
if type(statement) is parse.Play:
|
||||
comment = statement.path.lexeme.decode('utf-8')
|
||||
audio_index = state.audio_lookup[statement.path.lexeme]
|
||||
yield f"{{ .type = type::play, .play = {{ .audioIndex = {audio_index}, /* FIXME channel */ }} }}, // {pc} {comment}"
|
||||
yield f"{{ .type = type::play, .play = {{ .audioIndex = {audio_index} }} }}, // {pc} {comment}"
|
||||
elif type(statement) is parse.Scene:
|
||||
key = lhs_key(statement.name)
|
||||
if key in state.images_lookup:
|
||||
@ -190,7 +204,10 @@ def pass2_statement(state, pc, statement):
|
||||
elif type(statement) is parse.Return:
|
||||
yield f"{{ .type = type::_return }}, // {pc}"
|
||||
elif type(statement) is parse.Stop:
|
||||
yield f"{{ .type = type::stop, .stop = {{ /* FIXME channel */ }} }}, // {pc}"
|
||||
audio_index = state.channel_lookup[statement.channel.lexeme]
|
||||
fadeout = statement.fadeout.lexeme
|
||||
channel = statement.channel.lexeme.decode('utf-8')
|
||||
yield f"{{ .type = type::stop, .stop = {{ .audioIndex = {audio_index}, .fadeout = {float(fadeout)} }} }}, // {pc} {channel}"
|
||||
elif type(statement) is parse.Pause:
|
||||
duration = statement.duration.lexeme
|
||||
yield f"{{ .type = type::pause, .pause = {{ .duration = {duration} }} }}, // {pc}"
|
||||
@ -230,6 +247,7 @@ def pass2_characters(state):
|
||||
|
||||
def pass2_audio(state):
|
||||
yield "const language::audio audio[] = {"
|
||||
reverse_channel = {v: k for k, v in state.channel_lookup.items()}
|
||||
for audio, i in sorted(state.audio_lookup.items(), key=lambda kv: kv[1]):
|
||||
orig_path = audio.decode('utf-8')
|
||||
path = orig_path
|
||||
@ -239,7 +257,12 @@ def pass2_audio(state):
|
||||
path = path.removesuffix(".ogg")
|
||||
else:
|
||||
assert False, path
|
||||
yield f"{{ .path = \"{path}.opus\" }}, // {i} {orig_path}"
|
||||
name = audio
|
||||
channel_name = reverse_channel[i].decode('utf-8') if i in reverse_channel else None
|
||||
name = f"\"{channel_name}\"" if channel_name is not None else "nullptr"
|
||||
loop = loops[channel_name] if channel_name in loops else 0
|
||||
assert loop < 20_000, loop
|
||||
yield f"{{ .path = \"audio/{path}.opus.bin\", .loop_end = {float(loop)} }}, // {i} {orig_path}"
|
||||
yield "};"
|
||||
yield "const int audio_length = (sizeof (audio)) / (sizeof (audio[0]));"
|
||||
|
||||
@ -297,6 +320,7 @@ image _internal_flowers = "flowers.png"
|
||||
characters_lookup = dict(),
|
||||
labels_lookup = dict(),
|
||||
audio_lookup = dict(),
|
||||
channel_lookup = dict(),
|
||||
string_lookup = dict(),
|
||||
global_identifiers = set(),
|
||||
)
|
||||
|
||||
275
src/audio.cpp
Normal file
275
src/audio.cpp
Normal file
@ -0,0 +1,275 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <opus/opus.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "file.h"
|
||||
#include "audio.h"
|
||||
#include "new.h"
|
||||
#include "minmax.h"
|
||||
#include "renpy/language.h"
|
||||
|
||||
namespace audio {
|
||||
|
||||
static int const frame_samples = 960; // 20 milliseconds @ 48kHz
|
||||
static int const sample_rate = 48000;
|
||||
static int const channels = 2;
|
||||
static int const sample_size = (sizeof (int16_t));
|
||||
|
||||
static int const max_frame_size = 960 * 3; // 20ms at 48kHz
|
||||
static int const max_packet_size = 1275;
|
||||
|
||||
static int const half_period_samples = sample_rate / 2;
|
||||
static int const half_period_size = half_period_samples * sample_size * channels;
|
||||
|
||||
//
|
||||
|
||||
static SDL_AudioStream * audio_stream;
|
||||
static SDL_AudioSpec audio_spec;
|
||||
|
||||
static OpusDecoder * opus_decoder;
|
||||
|
||||
struct AudioBuffer {
|
||||
renpy::language::audio const * audio;
|
||||
int16_t * buf;
|
||||
uint32_t sample_count;
|
||||
};
|
||||
|
||||
struct AudioInstance {
|
||||
int audio_index;
|
||||
AudioBuffer * audio_buffer;
|
||||
uint32_t sample_index;
|
||||
uint32_t tail_index;
|
||||
uint32_t fadeout_end;
|
||||
uint32_t fadeout_index;
|
||||
};
|
||||
|
||||
static AudioBuffer * audio_buffers;
|
||||
static int audio_buffers_count;
|
||||
|
||||
constexpr int max_audio_instances = 128;
|
||||
static AudioInstance audio_instances[max_audio_instances];
|
||||
static int audio_instances_count;
|
||||
|
||||
void init()
|
||||
{
|
||||
audio_spec.channels = channels;
|
||||
audio_spec.format = SDL_AUDIO_S16LE;
|
||||
audio_spec.freq = sample_rate;
|
||||
audio_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec, NULL, NULL);
|
||||
assert(audio_stream);
|
||||
SDL_ResumeAudioStreamDevice(audio_stream);
|
||||
|
||||
int err;
|
||||
opus_decoder = opus_decoder_create(sample_rate, channels, &err);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "opus_decoder_create: %s\n", opus_strerror(err));
|
||||
assert(!"opus_decoder_create");
|
||||
}
|
||||
|
||||
audio_instances_count = 0;
|
||||
}
|
||||
|
||||
void decode(char const * const filename, AudioBuffer * audio_buffer)
|
||||
{
|
||||
uint32_t size;
|
||||
uint8_t const * buf = (uint8_t const *)file::open(filename, &size);
|
||||
assert(buf != nullptr);
|
||||
|
||||
uint32_t samples_count = (buf[3] << 24)
|
||||
| (buf[2] << 16)
|
||||
| (buf[1] << 8)
|
||||
| (buf[0] << 0);
|
||||
uint32_t offset = 4;
|
||||
uint32_t samples_decoded = 0;
|
||||
uint32_t samples_copied = 0;
|
||||
|
||||
int err = opus_decoder_ctl(opus_decoder, OPUS_RESET_STATE);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "opus_encoder_ctl(OPUS_RESET_STATE): %s\n", opus_strerror(err));
|
||||
assert(!"opus_encoder_ctl");
|
||||
}
|
||||
|
||||
int16_t * output_buf = NewM<int16_t>(channels * samples_count);
|
||||
|
||||
// decode packets
|
||||
while (offset < size) {
|
||||
uint16_t packet_size = (buf[offset + 1] << 8) | (buf[offset + 0] << 0);
|
||||
offset += 2;
|
||||
|
||||
assert(offset + packet_size <= size);
|
||||
int16_t decode_buf[max_frame_size * channels];
|
||||
int frame_size = opus_decode(opus_decoder, &buf[offset], packet_size, decode_buf, max_frame_size, 0);
|
||||
if (frame_size < 0) {
|
||||
fprintf(stderr, "opus_decode: %s\n", opus_strerror(frame_size));
|
||||
assert(!"opus_decode\n");
|
||||
}
|
||||
samples_decoded += frame_size;
|
||||
|
||||
uint32_t copy_samples = min(samples_decoded, samples_count) - samples_copied;
|
||||
memcpy(&output_buf[samples_copied * channels], decode_buf, copy_samples * channels * sample_size);
|
||||
|
||||
samples_copied += copy_samples;
|
||||
offset += packet_size;
|
||||
}
|
||||
|
||||
printf("copied %d decoded %d count %d\n", samples_copied, samples_decoded, samples_count);
|
||||
assert(samples_decoded >= samples_count);
|
||||
assert(samples_copied == samples_count);
|
||||
|
||||
audio_buffer->buf = output_buf;
|
||||
audio_buffer->sample_count = samples_count;
|
||||
|
||||
assert(audio_buffer->sample_count / 2);
|
||||
}
|
||||
|
||||
void load(renpy::language::audio const * const audio, int count)
|
||||
{
|
||||
audio_buffers = NewM<AudioBuffer>(count);
|
||||
audio_buffers_count = count;
|
||||
for (int i = 0; i < count; i++) {
|
||||
audio_buffers[i].audio = &audio[i];
|
||||
decode(audio[i].path, &audio_buffers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void play(int audio_index)
|
||||
{
|
||||
assert(audio_index >= 0 && audio_index < audio_buffers_count);
|
||||
assert(audio_instances_count < max_audio_instances);
|
||||
|
||||
AudioInstance & instance = audio_instances[audio_instances_count++];
|
||||
|
||||
instance.audio_index = (int)audio_index;
|
||||
instance.audio_buffer = &audio_buffers[audio_index];
|
||||
instance.sample_index = 0;
|
||||
instance.tail_index = audio_buffers[audio_index].sample_count;
|
||||
instance.fadeout_end = 0;
|
||||
instance.fadeout_index = 0;
|
||||
}
|
||||
|
||||
void stop(int audio_index, double fadeout)
|
||||
{
|
||||
assert(audio_index >= 0 && audio_index < audio_buffers_count);
|
||||
|
||||
for (int i = 0; i < audio_instances_count; i++) {
|
||||
if (audio_instances[i].audio_index == audio_index) {
|
||||
if (audio_instances[i].fadeout_end == 0) {
|
||||
fprintf(stderr, "audio: stop instance %d index %d\n", i, audio_index);
|
||||
audio_instances[i].fadeout_end = fadeout * (double)sample_rate;
|
||||
audio_instances[i].fadeout_index = 0;
|
||||
} else {
|
||||
fprintf(stderr, "audio: duplicate stop on instance %d index %d\n", i, audio_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void saturation_add(int16_t * mix_buffer, int32_t value)
|
||||
{
|
||||
int32_t mix_value = *mix_buffer;
|
||||
mix_value += value;
|
||||
if (mix_value > 32767)
|
||||
mix_value = 32767;
|
||||
if (mix_value < -32768)
|
||||
mix_value = -32768;
|
||||
*mix_buffer = mix_value;
|
||||
}
|
||||
|
||||
static inline void remove_instance(int instance_index)
|
||||
{
|
||||
fprintf(stderr, "removed instance %d index %d\n", instance_index, audio_instances[instance_index].audio_index);
|
||||
|
||||
for (int i = instance_index; i < (audio_instances_count - 1); i++) {
|
||||
audio_instances[i] = audio_instances[i + 1];
|
||||
}
|
||||
audio_instances_count -= 1;
|
||||
}
|
||||
|
||||
static inline void update_instance(int16_t * mix_buffer, AudioInstance & instance)
|
||||
{
|
||||
int16_t const * const buf = instance.audio_buffer->buf;
|
||||
uint32_t const sample_count = instance.audio_buffer->sample_count;
|
||||
uint32_t const loop_end = instance.audio_buffer->audio->loop_end * (double)sample_rate;
|
||||
|
||||
uint32_t mix_index = 0;
|
||||
for (int i = 0; i < half_period_samples; i++) {
|
||||
if (loop_end != 0) {
|
||||
if (instance.sample_index >= loop_end) {
|
||||
instance.sample_index = 0;
|
||||
instance.tail_index = loop_end;
|
||||
}
|
||||
} else if (instance.sample_index >= sample_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (instance.fadeout_end != 0 && instance.fadeout_index >= instance.fadeout_end) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(instance.sample_index < sample_count);
|
||||
assert(instance.tail_index <= sample_count);
|
||||
|
||||
|
||||
double fadeout = 1.0;
|
||||
if (instance.fadeout_end != 0) {
|
||||
fadeout = 1.0 - ((double)instance.fadeout_index / (double)instance.fadeout_end);
|
||||
}
|
||||
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
int32_t value = buf[instance.sample_index * channels + ch];
|
||||
if (instance.tail_index != sample_count) {
|
||||
value += buf[instance.tail_index * channels + ch];
|
||||
}
|
||||
saturation_add(&mix_buffer[mix_index * channels + ch], (double)value * fadeout);
|
||||
}
|
||||
instance.sample_index += 1;
|
||||
instance.fadeout_index += 1;
|
||||
if (instance.tail_index != sample_count) {
|
||||
instance.tail_index += 1;
|
||||
}
|
||||
|
||||
mix_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool should_cull_instance(AudioInstance & instance)
|
||||
{
|
||||
if (instance.audio_buffer->audio->loop_end != 0.0 && instance.sample_index >= instance.audio_buffer->sample_count) {
|
||||
return true;
|
||||
}
|
||||
if (instance.fadeout_end != 0 && instance.fadeout_index >= instance.fadeout_end) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void update()
|
||||
{
|
||||
if (SDL_GetAudioStreamQueued(audio_stream) >= half_period_size)
|
||||
return;
|
||||
|
||||
int16_t mix_buffer[half_period_samples * channels];
|
||||
memset(mix_buffer, 0, (sizeof (mix_buffer)));
|
||||
|
||||
for (int i = 0; i < audio_instances_count; i++) {
|
||||
update_instance(mix_buffer, audio_instances[i]);
|
||||
}
|
||||
|
||||
bool culled = true;
|
||||
while (culled) {
|
||||
culled = false;
|
||||
for (int i = 0; i < audio_instances_count; i++) {
|
||||
if (should_cull_instance(audio_instances[i])) {
|
||||
culled = true;
|
||||
remove_instance(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_PutAudioStreamData(audio_stream, (void *)mix_buffer, half_period_size);
|
||||
}
|
||||
}
|
||||
29
src/file.cpp
29
src/file.cpp
@ -1,3 +1,4 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@ -7,6 +8,7 @@
|
||||
|
||||
#include "pack.h"
|
||||
#include "file.h"
|
||||
#include "zlib.h"
|
||||
|
||||
extern "C" {
|
||||
#ifdef __APPLE__
|
||||
@ -22,18 +24,41 @@ extern "C" {
|
||||
#endif
|
||||
};
|
||||
|
||||
uint8_t * decompressed_start = NULL;
|
||||
|
||||
namespace file {
|
||||
|
||||
void init()
|
||||
{
|
||||
uint32_t * header = (uint32_t *)files_pack_start;
|
||||
if (header[0] != 0x56c8f1cb) {
|
||||
fprintf(stderr, "invalid compressed header magic: %08x expected magic value: %08x\n", header[0], 0x56c8f1cb);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
printf("decompressed size %d\n", header[1]);
|
||||
printf("compressed size %d\n", header[2]);
|
||||
|
||||
uint8_t * dest = (uint8_t *)malloc(header[1]);
|
||||
|
||||
uint64_t dest_len = header[1];
|
||||
uint8_t * src = (uint8_t *)&header[3];
|
||||
uint64_t src_len = header[2];
|
||||
int ret = uncompress2(dest, &dest_len, src, &src_len);
|
||||
assert(ret == Z_OK);
|
||||
decompressed_start = dest;
|
||||
}
|
||||
|
||||
void const * open(const char * filename, uint32_t * out_size)
|
||||
{
|
||||
assert(decompressed_start != NULL);
|
||||
fprintf(stderr, "(pack) filename: %s\n", filename);
|
||||
|
||||
pack::header const * header = (pack::header const *)&files_pack_start[0];
|
||||
pack::header const * header = (pack::header const *)&decompressed_start[0];
|
||||
if (header->magic != pack::magic_value) {
|
||||
fprintf(stderr, "invalid header magic: %08x expected magic value: %08x\n", header->magic, pack::magic_value);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
ptrdiff_t data = (ptrdiff_t)&files_pack_start[header->header_size];
|
||||
ptrdiff_t data = (ptrdiff_t)&decompressed_start[header->header_size];
|
||||
|
||||
for (unsigned int i = 0; i < header->entry_count; i++) {
|
||||
if (strcmp(header->entry[i].filename, filename) == 0) {
|
||||
|
||||
14
src/main.cpp
14
src/main.cpp
@ -24,10 +24,13 @@
|
||||
#include "renpy/vulkan.h"
|
||||
#include "renpy/interpreter.h"
|
||||
#include "renpy/interact.h"
|
||||
#include "renpy/script.h"
|
||||
|
||||
#include "scenes/shadow_test/shadow_test.h"
|
||||
#include "scenes/eidelwind/eidelwind.h"
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
VkInstance instance{ VK_NULL_HANDLE };
|
||||
VkDevice device{ VK_NULL_HANDLE };
|
||||
VkQueue queue{ VK_NULL_HANDLE };
|
||||
@ -355,7 +358,10 @@ void gamepad_update(view & viewState)
|
||||
|
||||
int main()
|
||||
{
|
||||
SDL_CHECK(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD));
|
||||
file::init();
|
||||
|
||||
SDL_InitFlags init_flags = SDL_INIT_VIDEO | SDL_INIT_GAMEPAD | SDL_INIT_AUDIO;
|
||||
SDL_CHECK(SDL_Init(init_flags));
|
||||
SDL_CHECK(SDL_Vulkan_LoadLibrary(NULL));
|
||||
volkInitialize();
|
||||
|
||||
@ -766,7 +772,11 @@ int main()
|
||||
|
||||
//collada_state.update(0);
|
||||
|
||||
audio::init();
|
||||
audio::load(renpy::script::audio, renpy::script::audio_length);
|
||||
|
||||
while (quit == false) {
|
||||
audio::update();
|
||||
interpreter_state.interpret();
|
||||
|
||||
SDL_Event event;
|
||||
@ -1214,7 +1224,7 @@ int main()
|
||||
vkDestroyCommandPool(device, commandPool, nullptr);
|
||||
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
SDL_QuitSubSystem(init_flags);
|
||||
SDL_Quit();
|
||||
|
||||
vkDestroyDevice(device, nullptr);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
.global _files_pack_start
|
||||
.global _files_pack_end
|
||||
_files_pack_start:
|
||||
.incbin "files.pack"
|
||||
// .incbin "files.pack"
|
||||
.incbin "files.pack.zlib"
|
||||
_files_pack_end:
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
#include "renpy/script.h"
|
||||
#include "renpy/interpreter.h"
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
namespace renpy {
|
||||
void interpreter::reset()
|
||||
{
|
||||
@ -64,19 +66,29 @@ namespace renpy {
|
||||
|
||||
switch (statement.type) {
|
||||
case language::type::play:
|
||||
fprintf(stderr, "interpret_one[%d]: play\n", pc);
|
||||
fprintf(stderr, "interpret_one[%d]: play %d\n", pc, statement.play.audioIndex);
|
||||
audio::play(statement.play.audioIndex);
|
||||
pc += 1;
|
||||
break;
|
||||
case language::type::stop:
|
||||
audio::stop(statement.stop.audioIndex, statement.stop.fadeout);
|
||||
pc += 1;
|
||||
break;
|
||||
case language::type::scene_color:
|
||||
fprintf(stderr, "interpret_one[%d]: scene_color\n", pc);
|
||||
backgroundIndex = -1;
|
||||
backgroundColor = statement.scene_color.color;
|
||||
|
||||
shownImagesCount = 0;
|
||||
say.stringIndex = -1;
|
||||
say.characterIndex = -1;
|
||||
pc += 1;
|
||||
break;
|
||||
case language::type::scene:
|
||||
fprintf(stderr, "interpret_one[%d]: scene\n", pc);
|
||||
assert(statement.scene.imageIndex < (uint32_t)script::images_length);
|
||||
backgroundIndex = statement.scene.imageIndex;
|
||||
|
||||
shownImagesCount = 0;
|
||||
say.stringIndex = -1;
|
||||
say.characterIndex = -1;
|
||||
|
||||
@ -194,15 +194,14 @@ const language::character characters[] = {
|
||||
const int characters_length = (sizeof (characters)) / (sizeof (characters[0]));
|
||||
|
||||
const language::audio audio[] = {
|
||||
{ .path = "sfx/Chime.opus" }, // 0 sfx/Chime.ogg
|
||||
{ .path = "sfx/MistAmbience.opus" }, // 1 sfx/MistAmbience.ogg
|
||||
{ .path = "music/TinyForestMinstrels.opus" }, // 2 music/TinyForestMinstrels.ogg
|
||||
{ .path = "n5test.opus" }, // 3 n5test.ogg
|
||||
{ .path = "music/PhrygianButterflies.opus" }, // 4 music/PhrygianButterflies.ogg
|
||||
{ .path = "music/Poem1.opus" }, // 5 music/Poem1.ogg
|
||||
{ .path = "placeholdermeow.opus" }, // 6 placeholdermeow.mp3
|
||||
{ .path = "music/ScaredMice.opus" }, // 7 music/ScaredMice.ogg
|
||||
{ .path = "music/WheatFields.opus" }, // 8 music/WheatFields.ogg
|
||||
{ .path = "audio/sfx/Chime.opus.bin", .loop_end = 0.0 }, // 0 sfx/Chime.ogg
|
||||
{ .path = "audio/sfx/MistAmbience.opus.bin", .loop_end = 22.0 }, // 1 sfx/MistAmbience.ogg
|
||||
{ .path = "audio/music/TinyForestMinstrels.opus.bin", .loop_end = 44.0 }, // 2 music/TinyForestMinstrels.ogg
|
||||
{ .path = "audio/music/PhrygianButterflies.opus.bin", .loop_end = 40.125 }, // 3 music/PhrygianButterflies.ogg
|
||||
{ .path = "audio/music/Poem1.opus.bin", .loop_end = 0.0 }, // 4 music/Poem1.ogg
|
||||
{ .path = "audio/placeholdermeow.opus.bin", .loop_end = 0.0 }, // 5 placeholdermeow.mp3
|
||||
{ .path = "audio/music/ScaredMice.opus.bin", .loop_end = 8.0 }, // 6 music/ScaredMice.ogg
|
||||
{ .path = "audio/music/WheatFields.opus.bin", .loop_end = 34.0 }, // 7 music/WheatFields.ogg
|
||||
};
|
||||
|
||||
const int audio_length = (sizeof (audio)) / (sizeof (audio[0]));
|
||||
@ -222,8 +221,8 @@ const language::image images[] = {
|
||||
const int images_length = (sizeof (images)) / (sizeof (images[0]));
|
||||
|
||||
const language::option options[] = {
|
||||
{ .string = "Complain", .statementIndex = 18 }, // 0
|
||||
{ .string = "Rationalize", .statementIndex = 26 }, // 1
|
||||
{ .string = "Complain", .statementIndex = 19 }, // 0
|
||||
{ .string = "Rationalize", .statementIndex = 27 }, // 1
|
||||
{ .string = "Good idea", .statementIndex = 54 }, // 2
|
||||
{ .string = "I am too tired", .statementIndex = 61 }, // 3
|
||||
{ .string = "Beg for mercy", .statementIndex = 78 }, // 4
|
||||
@ -233,45 +232,45 @@ const language::option options[] = {
|
||||
const int options_length = (sizeof (options)) / (sizeof (options[0]));
|
||||
|
||||
const language::statement statements[] = {
|
||||
{ .type = type::play, .play = { .audioIndex = 0, /* FIXME channel */ } }, // 0 sfx/Chime.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 1, /* FIXME channel */ } }, // 1 sfx/MistAmbience.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 0 } }, // 0 sfx/Chime.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 1 } }, // 1 sfx/MistAmbience.ogg
|
||||
{ .type = type::scene_color, .scene_color = { .color = 0xffffff } }, // 2 bgwhite
|
||||
{ .type = type::dissolve, .dissolve = { .duration = 3.0 } }, // 3
|
||||
{ .type = type::say, .say = { .characterIndex = 4, .stringIndex = 0 } }, // 4 n "Far over the mountains of Almystice"
|
||||
{ .type = type::say, .say = { .characterIndex = 4, .stringIndex = 1 } }, // 5 n "Beyond the tumultuous waters of the Lilac Bay"
|
||||
{ .type = type::say, .say = { .characterIndex = 4, .stringIndex = 2 } }, // 6 n "And across the vast fields of Alysen"
|
||||
{ .type = type::play, .play = { .audioIndex = 2, /* FIXME channel */ } }, // 7 music/TinyForestMinstrels.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 2 } }, // 7 music/TinyForestMinstrels.ogg
|
||||
{ .type = type::say, .say = { .characterIndex = 4, .stringIndex = 3 } }, // 8 n "Tiny minstrels can be heard amongst the trees"
|
||||
{ .type = type::scene, .scene = { .imageIndex = 1 } }, // 9 bgforest1
|
||||
{ .type = type::dissolve, .dissolve = { .duration = 3.0 } }, // 10
|
||||
{ .type = type::show, .show = { .imageIndex = 8, .transformIndex = transform::left } }, // 11 al
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 4 } }, // 12 a "Are we almost there?"
|
||||
{ .type = type::show, .show = { .imageIndex = 7, .transformIndex = transform::right } }, // 13 ei
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 5 } }, // 14 e "Hmmm... Not really"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 6 } }, // 15 a "How much further have we to go?"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 7 } }, // 16 e "About two more moons"
|
||||
{ .type = type::menu, .menu = { .count = 2, .optionIndex = 0 } }, // 17 "Complain", "Rationalize"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 8 } }, // 18 a "We are still sooo far awayyy"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 9 } }, // 19 e "And it will be even further if you dont stop complaining"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 10 } }, // 20 a "Easy for you to say, all you have to carry is a little memory pipe!"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 11 } }, // 21 a "I'm tired ><"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 12 } }, // 22 e "Don't start whining now!"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 13 } }, // 23 e "You need to remember why we have come all this way"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 14 } }, // 24 a "I understand... I suppose it is for an important purpose"
|
||||
{ .type = type::jump, .jump = { .statementIndex = 28 } }, // 25 internal jump (b'__menu_end', 0)
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 14 } }, // 26 a "I understand... I suppose it is for an important purpose"
|
||||
{ .type = type::jump, .jump = { .statementIndex = 28 } }, // 27 internal jump (b'__menu_end', 0)
|
||||
{ .type = type::jump, .jump = { .statementIndex = 29 } }, // 28 mainbranch1
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 15 } }, // 29 e "We're almost out of the forest, we can take a little break once we clear the tree line"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 16 } }, // 30 a "Is that where the flora field is?"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 17 } }, // 31 e "Why yes, If I remember correctly, it should be just up ahead"
|
||||
{ .type = type::stop, .stop = { /* FIXME channel */ } }, // 32
|
||||
{ .type = type::scene_color, .scene_color = { .color = 0xffffff } }, // 33 bgwhite
|
||||
{ .type = type::play, .play = { .audioIndex = 0, /* FIXME channel */ } }, // 34 sfx/Chime.ogg
|
||||
{ .type = type::dissolve, .dissolve = { .duration = 1.0 } }, // 35
|
||||
{ .type = type::voice, .voice = { .audioIndex = 3 } }, // 36 n5test.ogg
|
||||
{ .type = type::stop, .stop = { .audioIndex = 2, .fadeout = 5.5 } }, // 9 TinyForestMinstrels
|
||||
{ .type = type::scene, .scene = { .imageIndex = 1 } }, // 10 bgforest1
|
||||
{ .type = type::dissolve, .dissolve = { .duration = 3.0 } }, // 11
|
||||
{ .type = type::show, .show = { .imageIndex = 8, .transformIndex = transform::left } }, // 12 al
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 4 } }, // 13 a "Are we almost there?"
|
||||
{ .type = type::show, .show = { .imageIndex = 7, .transformIndex = transform::right } }, // 14 ei
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 5 } }, // 15 e "Hmmm... Not really"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 6 } }, // 16 a "How much further have we to go?"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 7 } }, // 17 e "About two more moons"
|
||||
{ .type = type::menu, .menu = { .count = 2, .optionIndex = 0 } }, // 18 "Complain", "Rationalize"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 8 } }, // 19 a "We are still sooo far awayyy"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 9 } }, // 20 e "And it will be even further if you dont stop complaining"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 10 } }, // 21 a "Easy for you to say, all you have to carry is a little memory pipe!"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 11 } }, // 22 a "I'm tired ><"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 12 } }, // 23 e "Don't start whining now!"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 13 } }, // 24 e "You need to remember why we have come all this way"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 14 } }, // 25 a "I understand... I suppose it is for an important purpose"
|
||||
{ .type = type::jump, .jump = { .statementIndex = 29 } }, // 26 internal jump (b'__menu_end', 0)
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 14 } }, // 27 a "I understand... I suppose it is for an important purpose"
|
||||
{ .type = type::jump, .jump = { .statementIndex = 29 } }, // 28 internal jump (b'__menu_end', 0)
|
||||
{ .type = type::jump, .jump = { .statementIndex = 30 } }, // 29 mainbranch1
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 15 } }, // 30 e "We're almost out of the forest, we can take a little break once we clear the tree line"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 16 } }, // 31 a "Is that where the flora field is?"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 17 } }, // 32 e "Why yes, If I remember correctly, it should be just up ahead"
|
||||
{ .type = type::stop, .stop = { .audioIndex = 2, .fadeout = 5.5 } }, // 33 TinyForestMinstrels
|
||||
{ .type = type::scene_color, .scene_color = { .color = 0xffffff } }, // 34 bgwhite
|
||||
{ .type = type::play, .play = { .audioIndex = 0 } }, // 35 sfx/Chime.ogg
|
||||
{ .type = type::dissolve, .dissolve = { .duration = 1.0 } }, // 36
|
||||
{ .type = type::say, .say = { .characterIndex = 4, .stringIndex = 18 } }, // 37 n "As the minstrel mice girls continue along the path, the forest opens up into a beautiful field of flowers"
|
||||
{ .type = type::play, .play = { .audioIndex = 4, /* FIXME channel */ } }, // 38 music/PhrygianButterflies.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 3 } }, // 38 music/PhrygianButterflies.ogg
|
||||
{ .type = type::scene, .scene = { .imageIndex = 3 } }, // 39 bgflower1
|
||||
{ .type = type::dissolve, .dissolve = { .duration = 1.0 } }, // 40
|
||||
{ .type = type::show, .show = { .imageIndex = 7, .transformIndex = transform::right } }, // 41 ei
|
||||
@ -287,26 +286,26 @@ const language::statement statements[] = {
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 27 } }, // 51 e "Yah yah"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 28 } }, // 52 e "Anyways, shall I recite a tale?"
|
||||
{ .type = type::menu, .menu = { .count = 2, .optionIndex = 2 } }, // 53 "Good idea", "I am too tired"
|
||||
{ .type = type::stop, .stop = { /* FIXME channel */ } }, // 54
|
||||
{ .type = type::stop, .stop = { .audioIndex = 3, .fadeout = 4.2 } }, // 54 PhrygianButterflies
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 29 } }, // 55 a "Why dont you sing the story of Eleanor the Hero!"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 30 } }, // 56 e "Sure"
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 31 } }, // 57 a "..."
|
||||
{ .type = type::play, .play = { .audioIndex = 5, /* FIXME channel */ } }, // 58 music/Poem1.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 4 } }, // 58 music/Poem1.ogg
|
||||
{ .type = type::pause, .pause = { .duration = 40 } }, // 59
|
||||
{ .type = type::jump, .jump = { .statementIndex = 65 } }, // 60 internal jump (b'__menu_end', 1)
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 32 } }, // 61 e "Serves you right for scaring those elephant-dogs"
|
||||
{ .type = type::stop, .stop = { /* FIXME channel */ } }, // 62
|
||||
{ .type = type::stop, .stop = { .audioIndex = 3, .fadeout = 4.2 } }, // 62 PhrygianButterflies
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 33 } }, // 63 a "They were asking for it, you know"
|
||||
{ .type = type::jump, .jump = { .statementIndex = 65 } }, // 64 internal jump (b'__menu_end', 1)
|
||||
{ .type = type::jump, .jump = { .statementIndex = 66 } }, // 65 mainbranch2
|
||||
{ .type = type::hide, .hide = { .imageIndex = 7 } }, // 66 ei
|
||||
{ .type = type::show, .show = { .imageIndex = 6, .transformIndex = transform::right } }, // 67 catw
|
||||
{ .type = type::show, .show = { .imageIndex = 7, .transformIndex = transform::centerleft } }, // 68 ei
|
||||
{ .type = type::voice, .voice = { .audioIndex = 6 } }, // 69 placeholdermeow.mp3
|
||||
{ .type = type::voice, .voice = { .audioIndex = 5 } }, // 69 placeholdermeow.mp3
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 34 } }, // 70 c "Rawrrrr"
|
||||
{ .type = type::hide, .hide = { .imageIndex = 6 } }, // 71 catw
|
||||
{ .type = type::show, .show = { .imageIndex = 5, .transformIndex = transform::right } }, // 72 cat
|
||||
{ .type = type::play, .play = { .audioIndex = 7, /* FIXME channel */ } }, // 73 music/ScaredMice.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 6 } }, // 73 music/ScaredMice.ogg
|
||||
{ .type = type::say, .say = { .characterIndex = 3, .stringIndex = 35 } }, // 74 mg "AHHHHHHHHHH!!!!!"
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 36 } }, // 75 c "Nyanyanyanya"
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 37 } }, // 76 c "Well, what do we have here? If it isn't two little meowse girls, all alone amongst the flowers"
|
||||
@ -317,9 +316,9 @@ const language::statement statements[] = {
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 40 } }, // 81 e "Please don't eat us, miss kitty cat!!! ><"
|
||||
{ .type = type::jump, .jump = { .statementIndex = 83 } }, // 82 internal jump (b'__menu_end', 2)
|
||||
{ .type = type::jump, .jump = { .statementIndex = 84 } }, // 83 mainbranch3
|
||||
{ .type = type::stop, .stop = { /* FIXME channel */ } }, // 84
|
||||
{ .type = type::stop, .stop = { .audioIndex = 6, .fadeout = 2.0 } }, // 84 ScaredMice
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 41 } }, // 85 c "I'm not gonna eat you nyanyanya"
|
||||
{ .type = type::play, .play = { .audioIndex = 2, /* FIXME channel */ } }, // 86 music/TinyForestMinstrels.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 2 } }, // 86 music/TinyForestMinstrels.ogg
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 42 } }, // 87 c "I just want to know what two little meowses are doing so very far away from home"
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 43 } }, // 88 c "Also, are you minstrels?"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 44 } }, // 89 e "Y-Yes"
|
||||
@ -341,8 +340,8 @@ const language::statement statements[] = {
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 60 } }, // 105 c "Well, no..."
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 61 } }, // 106 a "Then why are you traveling to Castle Alysen?"
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 62 } }, // 107 c "uhhh"
|
||||
{ .type = type::play, .play = { .audioIndex = 1, /* FIXME channel */ } }, // 108 sfx/MistAmbience.ogg
|
||||
{ .type = type::stop, .stop = { /* FIXME channel */ } }, // 109
|
||||
{ .type = type::play, .play = { .audioIndex = 1 } }, // 108 sfx/MistAmbience.ogg
|
||||
{ .type = type::stop, .stop = { .audioIndex = 2, .fadeout = 2.0 } }, // 109 TinyForestMinstrels
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 63 } }, // 110 c "I DONT NEED TO BE PRESSURED BY LITTLE MICE TO SAY ANYTHING!!!!"
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 64 } }, // 111 c "GOOD DAY!"
|
||||
{ .type = type::hide, .hide = { .imageIndex = 5 } }, // 112 cat
|
||||
@ -351,7 +350,7 @@ const language::statement statements[] = {
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 67 } }, // 115 a "She didn't seem so bad"
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 68 } }, // 116 e "Are you kidding? She's a crazy kitty!"
|
||||
{ .type = type::scene_color, .scene_color = { .color = 0xffffff } }, // 117 bgwhite
|
||||
{ .type = type::play, .play = { .audioIndex = 0, /* FIXME channel */ } }, // 118 sfx/Chime.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 0 } }, // 118 sfx/Chime.ogg
|
||||
{ .type = type::dissolve, .dissolve = { .duration = 3.0 } }, // 119
|
||||
{ .type = type::say, .say = { .characterIndex = 4, .stringIndex = 69 } }, // 120 n "After their encounter with the weird cat, the mice scurry out of the flower field and into the nearby meadow"
|
||||
{ .type = type::scene, .scene = { .imageIndex = 2 } }, // 121 bgforest2
|
||||
@ -364,7 +363,7 @@ const language::statement statements[] = {
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 73 } }, // 128 a "Did you hear that?!?!"
|
||||
{ .type = type::show, .show = { .imageIndex = 7, .transformIndex = transform::centerleft } }, // 129 ei
|
||||
{ .type = type::show, .show = { .imageIndex = 5, .transformIndex = transform::right } }, // 130 cat
|
||||
{ .type = type::play, .play = { .audioIndex = 4, /* FIXME channel */ } }, // 131 music/PhrygianButterflies.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 3 } }, // 131 music/PhrygianButterflies.ogg
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 74 } }, // 132 c "Hey there..."
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 75 } }, // 133 c "I apologize"
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 76 } }, // 134 c "I didn't mean to storm off like that"
|
||||
@ -427,15 +426,15 @@ const language::statement statements[] = {
|
||||
{ .type = type::hide, .hide = { .imageIndex = 5 } }, // 191 cat
|
||||
{ .type = type::say, .say = { .characterIndex = 0, .stringIndex = 132 } }, // 192 a "Sounds good!"
|
||||
{ .type = type::hide, .hide = { .imageIndex = 8 } }, // 193 al
|
||||
{ .type = type::stop, .stop = { /* FIXME channel */ } }, // 194
|
||||
{ .type = type::stop, .stop = { .audioIndex = 3, .fadeout = 2.0 } }, // 194 PhrygianButterflies
|
||||
{ .type = type::say, .say = { .characterIndex = 2, .stringIndex = 133 } }, // 195 e "Oh dear!"
|
||||
{ .type = type::hide, .hide = { .imageIndex = 7 } }, // 196 ei
|
||||
{ .type = type::scene_color, .scene_color = { .color = 0xffffff } }, // 197 bgwhite
|
||||
{ .type = type::play, .play = { .audioIndex = 0, /* FIXME channel */ } }, // 198 sfx/Chime.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 0 } }, // 198 sfx/Chime.ogg
|
||||
{ .type = type::dissolve, .dissolve = { .duration = 2.0 } }, // 199
|
||||
{ .type = type::say, .say = { .characterIndex = 4, .stringIndex = 134 } }, // 200 n "And so the mice girls follow the noble cat further towards their destination"
|
||||
{ .type = type::scene, .scene = { .imageIndex = 4 } }, // 201 bgwheatfield1
|
||||
{ .type = type::play, .play = { .audioIndex = 8, /* FIXME channel */ } }, // 202 music/WheatFields.ogg
|
||||
{ .type = type::play, .play = { .audioIndex = 7 } }, // 202 music/WheatFields.ogg
|
||||
{ .type = type::show, .show = { .imageIndex = 5, .transformIndex = transform::right } }, // 203 cat
|
||||
{ .type = type::dissolve, .dissolve = { .duration = 1.3 } }, // 204
|
||||
{ .type = type::say, .say = { .characterIndex = 1, .stringIndex = 135 } }, // 205 c "Nya"
|
||||
|
||||
9
tools/Makefile
Normal file
9
tools/Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
OPT = -O2
|
||||
CFLAGS = -I../../opus-dist/include
|
||||
CFLAGS = -I../include
|
||||
|
||||
opus_encode: opus_encode.c ../../opus-dist/lib/libopus.a
|
||||
gcc -o $@ $(OPT) $(CFLAGS) -lm $^
|
||||
|
||||
pack_file: pack_file.cpp
|
||||
g++ -o $@ $(OPT) $(CFLAGS) $^
|
||||
88
tools/compress.c
Normal file
88
tools/compress.c
Normal file
@ -0,0 +1,88 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "zlib.h"
|
||||
|
||||
/*
|
||||
ZEXTERN int ZEXPORT compress2(Bytef *dest, uLongf *destLen,
|
||||
const Bytef *source, uLong sourceLen,
|
||||
int level);
|
||||
*/
|
||||
|
||||
void const * read_file(const char * filename, int * out_size)
|
||||
{
|
||||
fprintf(stderr, "filename: %s\n", filename);
|
||||
|
||||
FILE * f = fopen(filename, "rb");
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, "fopen(%s): %s\n", filename, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int fseek_end_ret = fseek(f, 0, SEEK_END);
|
||||
if (fseek_end_ret < 0) {
|
||||
fprintf(stderr, "fseek(%s, SEEK_END): %s\n", filename, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t size = ftell(f);
|
||||
if (size < 0) {
|
||||
fprintf(stderr, "ftell(%s): %s\n", filename, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int fseek_set_ret = fseek(f, 0, SEEK_SET);
|
||||
if (fseek_set_ret < 0) {
|
||||
fprintf(stderr, "lseek(%s, SEEK_SET): %s\n", filename, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
rewind(f);
|
||||
|
||||
void * buf = malloc(size);
|
||||
|
||||
size_t read_size = fread(buf, 1, size, f);
|
||||
if (read_size != size) {
|
||||
fprintf(stderr, "fread(%s): %s\n", filename, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*out_size = size;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
int main(int argc, char const * const argv[])
|
||||
{
|
||||
assert(argc == 3);
|
||||
const char * in_filename = argv[1];
|
||||
const char * out_filename = argv[2];
|
||||
|
||||
int out_size;
|
||||
void const * buf = read_file(in_filename, &out_size);
|
||||
assert(buf != NULL);
|
||||
|
||||
void * dest = malloc(out_size * 0.001 + out_size + 12);
|
||||
|
||||
static_assert((sizeof (long unsigned int)) == (sizeof (uint64_t)));
|
||||
uint64_t dest_size = out_size;
|
||||
int level = 9;
|
||||
int zret = compress2(dest, &dest_size, buf, out_size, level);
|
||||
assert(zret == Z_OK);
|
||||
|
||||
FILE * f = fopen(out_filename, "wb");
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, "fopen(%s): %s\n", out_filename, strerror(errno));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
uint32_t header[3];
|
||||
header[0] = 0x56c8f1cb; // magic
|
||||
header[1] = out_size; // decompressed size
|
||||
header[2] = dest_size; // compressed size
|
||||
fwrite(header, (sizeof (uint32_t)), 3, f);
|
||||
fwrite(dest, dest_size, 1, f);
|
||||
}
|
||||
3
tools/compress.sh
Normal file
3
tools/compress.sh
Normal file
@ -0,0 +1,3 @@
|
||||
ZLIB=$HOME/zlib-1.3.2
|
||||
set -eux
|
||||
gcc -I$ZLIB compress.c $ZLIB/compress.o $ZLIB/deflate.o $ZLIB/trees.o $ZLIB/zutil.o $ZLIB/crc32.o $ZLIB/adler32.o -o compress
|
||||
121
tools/opus_encode.c
Normal file
121
tools/opus_encode.c
Normal file
@ -0,0 +1,121 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <opus/opus.h>
|
||||
|
||||
const int frame_size = 960; // 20ms at 48kHz
|
||||
const int sample_rate = 48000;
|
||||
const int channels = 2;
|
||||
const int bitrate = 128000;
|
||||
|
||||
const int max_packet_size = 3 * 1275;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
assert(argc == 3);
|
||||
int err;
|
||||
|
||||
OpusEncoder * encoder = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_AUDIO, &err);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "opus_encoder_create: %s\n", opus_strerror(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
err = opus_encoder_ctl(encoder, OPUS_SET_BITRATE(bitrate));
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "opus_encoder_ctl(OPUS_SET_BITRATE): %s\n", opus_strerror(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
char const * input_filename = argv[1];
|
||||
FILE * input_file = fopen(input_filename, "rb");
|
||||
if (input_file == NULL) {
|
||||
fprintf(stderr, "fopen(%s): %s\n", input_filename, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
char const * output_filename = argv[2];
|
||||
FILE * output_file = fopen(output_filename, "wb");
|
||||
if (input_file == NULL) {
|
||||
fprintf(stderr, "fopen(%s): %s\n", output_filename, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int16_t input[frame_size * channels];
|
||||
uint8_t input_buf[frame_size * channels * (sizeof (int16_t))];
|
||||
uint8_t encode_buf[max_packet_size];
|
||||
|
||||
int total_samples = 0;
|
||||
size_t header_write_1 = fwrite(&total_samples, 1, (sizeof (int)), output_file);
|
||||
if (header_write_1 != (sizeof (int))) {
|
||||
fprintf(stderr, "fwrite: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool more_data = true;
|
||||
while (more_data) {
|
||||
size_t samples = fread(input_buf, (sizeof (int16_t)) * channels, frame_size, input_file);
|
||||
if (samples == 0)
|
||||
break;
|
||||
|
||||
total_samples += samples;
|
||||
|
||||
if (samples != frame_size) {
|
||||
printf("padding short frame %ld\n", samples);
|
||||
more_data = false;
|
||||
for (int i = samples * channels; i < frame_size * channels; i++) {
|
||||
input_buf[i * 2 + 0] = 0;
|
||||
input_buf[i * 2 + 1] = 0;
|
||||
}
|
||||
}
|
||||
samples = frame_size;
|
||||
|
||||
for (int i = 0; i < channels * frame_size; i++) {
|
||||
// load little endian
|
||||
input[i] = (input_buf[2 * i + 1] << 8) | (input_buf[2 * i + 0] << 0);
|
||||
}
|
||||
|
||||
int bytes = opus_encode(encoder, input, frame_size, encode_buf, max_packet_size);
|
||||
if (bytes < 0) {
|
||||
fprintf(stderr, "opus_encode: %s\n", opus_strerror(bytes));
|
||||
return 1;
|
||||
}
|
||||
assert(bytes <= 1275);
|
||||
|
||||
int16_t encode_bytes = bytes;
|
||||
size_t bytes_write = fwrite(&encode_bytes, 1, (sizeof (encode_bytes)), output_file);
|
||||
if (bytes_write != (sizeof (encode_bytes))) {
|
||||
fprintf(stderr, "fwrite: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
size_t buf_write = fwrite(encode_buf, 1, bytes, output_file);
|
||||
if (buf_write != bytes) {
|
||||
fprintf(stderr, "fwrite: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
fflush(output_file);
|
||||
|
||||
int ret = fseek(output_file, SEEK_SET, 0);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "fseek: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t header_write_2 = fwrite(&total_samples, 1, (sizeof (int)), output_file);
|
||||
if (header_write_2 != (sizeof (int))) {
|
||||
fprintf(stderr, "fwrite: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
printf("total_samples: %d\n", total_samples);
|
||||
|
||||
fclose(output_file);
|
||||
fclose(input_file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user