Skip to content

Commit

Permalink
sound_convert sample type conversions
Browse files Browse the repository at this point in the history
wav_file simple wave file utility
fix basename misspelling in nsfplay/makefile
fix basename missing in cmd/makefile
  • Loading branch information
bbbradsmith committed May 14, 2024
1 parent cc8cf32 commit 15cd871
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 7 deletions.
2 changes: 2 additions & 0 deletions cmd/cmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <cerrno> // cerrno
#include <thread> // std::this_thread::sleep_for
#include "../shared/sound.h"
#include "../shared/sound_convert.h"
#include "../shared/wav_file.h"

// platform specific abstractions (platform.cpp)
void platform_setup(int argc, char** argv);
Expand Down
3 changes: 3 additions & 0 deletions cmd/cmd.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\shared\sound.cpp" />
<ClCompile Include="..\shared\wav_file.cpp" />
<ClCompile Include="cmd.cpp" />
<ClCompile Include="platform.cpp" />
<ClCompile Include="unit_test.cpp" />
Expand All @@ -180,6 +181,8 @@
<ClInclude Include="..\include\nsfplaycore.h" />
<ClInclude Include="..\include\nsfplayenums.h" />
<ClInclude Include="..\shared\sound.h" />
<ClInclude Include="..\shared\sound_convert.h" />
<ClInclude Include="..\shared\wav_file.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
Expand Down
3 changes: 3 additions & 0 deletions cmd/cmd.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
<ClCompile Include="platform.cpp" />
<ClCompile Include="unit_test.cpp" />
<ClCompile Include="..\shared\sound.cpp" />
<ClCompile Include="..\shared\wav_file.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\nsfplaycore.h" />
<ClInclude Include="..\include\nsfplayenums.h" />
<ClInclude Include="..\shared\sound.h" />
<ClInclude Include="..\shared\sound_convert.h" />
<ClInclude Include="..\shared\wav_file.h" />
</ItemGroup>
</Project>
6 changes: 3 additions & 3 deletions cmd/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ CXXFLAGS_ALL = $(CXXFLAGS) $(CXXFLAGS_EXTRA) $(INC_COMMON)
LDFLAGS_ALL = $(LDFLAGS) $(LDFLAGS_EXTRA) $(LDFLAGS_CMD)
LIBS =

SHARED_SRCS = ../shared/sound.cpp
SHARED_SRCS = ../shared/sound.cpp ../shared/wav_file.cpp
SHARED_OBJS = $(addprefix $(CMD_INTDIR)/shared/,$(notdir $(SHARED_SRCS:.cpp=.o)))
SHARED_DEPS = $(addprefix $(CMD_INTDIR)/shared/,$(notdir $(SHARED_SRCS:.cpp=.d)))

Expand All @@ -39,9 +39,9 @@ $(TARGET): $(OBJS) $(SHARED_OBJS) $(CORE) | $(dir $(TARGET))
$(STRIP_DEBUG)

$(CMD_INTDIR)/%.d: %.cpp | $(CMD_INTDIR)/
$(CXX) -M -MM -MF $@ -MT $(CMD_INTDIR)/$(notdir $<).o $(CXXFLAGS_ALL) -c $<
$(CXX) -M -MM -MF $@ -MT $(CMD_INTDIR)/$(basename $<).o $(CXXFLAGS_ALL) -c $<
$(CMD_INTDIR)/shared/%.d: ../shared/%.cpp | $(CMD_INTDIR)/shared/
$(CXX) -M -MM -MF $@ -MT $(CMD_INTDIR)/shared/$(notdir $<).o $(CXXFLAGS_ALL) -c $<
$(CXX) -M -MM -MF $@ -MT $(CMD_INTDIR)/shared/$(basename $(notdir $<)).o $(CXXFLAGS_ALL) -c $<

$(CMD_INTDIR)/%.o: %.cpp $(CMD_INTDIR)/%.d | $(CMD_INTDIR)/
$(CXX) -o $@ $(CXXFLAGS_ALL) -c $<
Expand Down
5 changes: 3 additions & 2 deletions include/nsfplaycore.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,11 @@ uint64_t nsfplay_samples_played(const NSFCore* core); // samples since song_play

// emulate and render sound samples
// - internal mixing is done at 32-bits
// - 4-bit overhead for clipping is intended, clamp at +/-268,435,436 ((1<<28)-1) then shift to desired bit size
// - every two elements of stereo_output alternates left channel, right channel (stereo_output length must be 2*samples)
// - 4-bit overhead for clipping is intended, clamp at +/- (1<<27)-1) if needed, then shift to desired bit size
// - every two elements of stereo_output alternates left channel, right channel (stereo_output length must be samples*2)
// - stereo_output can be NULL if the output isn't needed
// - returns number of samples rendered, may be less than samples if song is finished (will zero fill unused output samples)
// - see shared/sound_convert.h for conversion examples to various formats
uint32_t nsfplay_render(NSFCore* core, uint32_t samples, int32_t* stereo_output);

// manually trigger render buffer allocations if needed
Expand Down
4 changes: 2 additions & 2 deletions nsfplay/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ CXXFLAGS_ALL = $(CXXFLAGS) $(CXXFLAGS_EXTRA) $(INC_COMMON) $(WXL_CXXFLAGS) $(PAL
LDFLAGS_ALL = $(LDFLAGS) $(LDFLAGS_EXTRA) $(LDFLAGS_NSFPLAY)
LIBS = $(CORE) $(GUI) $(WXL_LIBS) $(PAL_LIBS)

SHARED_SRCS = ../shared/sound.cpp
SHARED_SRCS = ../shared/sound.cpp ../shared/wav_file.cpp
SHARED_OBJS = $(addprefix $(NSFPLAY_INTDIR)/shared/,$(notdir $(SHARED_SRCS:.cpp=.o)))
SHARED_DEPS = $(addprefix $(NSFPLAY_INTDIR)/shared/,$(notdir $(SHARED_SRCS:.cpp=.d)))

Expand All @@ -37,7 +37,7 @@ $(TARGET): $(OBJS) $(SHARED_OBJS) $(CORE) $(GUI) | $(dir $(TARGET))
$(NSFPLAY_INTDIR)/%.d: %.cpp | $(NSFPLAY_INTDIR)/
$(CXX) -M -MM -MF $@ -MT $(NSFPLAY_INTDIR)/$(basename $<).o $(CXXFLAGS_ALL) -c $<
$(NSFPLAY_INTDIR)/shared/%.d: ../shared/%.cpp | $(NSFPLAY_INTDIR)/shared/
$(CXX) -M -MM -MF $@ -MT $(NSFPLAY_INTDIR)/shared/$(basenamse $(notdir $<)).o $(CXXFLAGS_ALL) -c $<
$(CXX) -M -MM -MF $@ -MT $(NSFPLAY_INTDIR)/shared/$(basename $(notdir $<)).o $(CXXFLAGS_ALL) -c $<

$(NSFPLAY_INTDIR)/%.o: %.cpp $(NSFPLAY_INTDIR)/%.d | $(NSFPLAY_INTDIR)/
$(CXX) -o $@ $(CXXFLAGS_ALL) -c $<
Expand Down
3 changes: 3 additions & 0 deletions nsfplay/nsfplay.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,13 @@
<ClInclude Include="..\include\nsfplayenums.h" />
<ClInclude Include="..\include\nsfplaygui.h" />
<ClInclude Include="..\shared\sound.h" />
<ClInclude Include="..\shared\sound_convert.h" />
<ClInclude Include="..\shared\wav_file.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\shared\sound.cpp" />
<ClCompile Include="..\shared\wav_file.cpp" />
<ClCompile Include="nsfplay.cpp" />
</ItemGroup>
<ItemGroup>
Expand Down
9 changes: 9 additions & 0 deletions nsfplay/nsfplay.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
<ClInclude Include="..\shared\sound.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\shared\sound_convert.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\shared\wav_file.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="nsfplay.cpp">
Expand All @@ -38,6 +44,9 @@
<ClCompile Include="..\shared\sound.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\shared\wav_file.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="nsfplay.rc">
Expand Down
133 changes: 133 additions & 0 deletions shared/sound_convert.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#pragma once
// shared/sound_convert.h
// common sound render conversions
// NSFPlay internal 28-bit stereo to various outputs
// inlined to facilitate compiler optimizations, especially witha constant number of samples

#include <cstdint>

inline void sound_convert_sint32s(const uint32_t samples, const int32_t* input, int32_t* output);
inline void sound_convert_sint32m(const uint32_t samples, const int32_t* input, int32_t* output);
inline void sound_convert_sint24s(const uint32_t samples, const int32_t* input, uint8_t* output);
inline void sound_convert_sint24m(const uint32_t samples, const int32_t* input, uint8_t* output);
inline void sound_convert_sint16s(const uint32_t samples, const int32_t* input, int16_t* output);
inline void sound_convert_sint16m(const uint32_t samples, const int32_t* input, int16_t* output);
inline void sound_convert_uint8s( const uint32_t samples, const int32_t* input, uint8_t* output);
inline void sound_convert_uint8m( const uint32_t samples, const int32_t* input, uint8_t* output);

//
// Inline Implementation
//

inline int32_t sound_convert_clamp(const int32_t s)
{
static const int32_t SCMAX = ((1<<27)-1);
static const int32_t SCMIN = -((1<<27)-1);
if (s > SCMAX) return SCMAX;
else if (s < SCMIN) return SCMIN;
return s;
}

// single sample clamp and convert

inline int32_t sound_convert_clamp32(const int32_t s)
{
return sound_convert_clamp(s) << 4;
}

inline int32_t sound_convert_clamp24(const int32_t s)
{
return sound_convert_clamp(s) >> 4;
}

inline int16_t sound_convert_clamp16(const int32_t s)
{
return int16_t(sound_convert_clamp(s) >> 12);
}

inline uint8_t sound_convert_clamp8(const int32_t s)
{
return uint8_t((sound_convert_clamp(s) >> 20) + (1<<7));
}

// stereo to mono single sample clamp and convert

inline int32_t sound_convert_clamp32m(const int32_t s0, const int32_t s1)
{
return sound_convert_clamp32((s0>>1)+(s1>>1)); // shifting first avoids reducing overhead
}

inline int32_t sound_convert_clamp24m(const int32_t s0, const int32_t s1)
{
return sound_convert_clamp24((s0>>1)+(s1>>1));
}

inline int16_t sound_convert_clamp16m(const int32_t s0, const int32_t s1)
{
return sound_convert_clamp16((s0>>1)+(s1>>1));
}

inline uint8_t sound_convert_clamp8m(const int32_t s0, const int32_t s1)
{
return sound_convert_clamp8((s0>>1)+(s1>>1));
}

// batch conversion

inline void sound_convert_sint32s(const uint32_t samples, const int32_t* input, int32_t* output)
{
for (uint32_t i=0; i<(samples*2); ++i)
output[i] = sound_convert_clamp32(input[i]);
}

inline void sound_convert_sint32m(const uint32_t samples, const int32_t* input, int32_t* output)
{
for (uint32_t i=0; i<samples; ++i)
output[i] = sound_convert_clamp32m(input[(i*2)+0],input[(i*2)+1]);
}

inline void sound_convert_sint24s(const uint32_t samples, const int32_t* input, uint8_t* output)
{
for (uint32_t i=0,j=0; i<(samples*3); i+=3,j+=1)
{
const int32_t s = sound_convert_clamp24(input[j]);
output[i+0] = uint8_t((s >> 0) & 0xFF);
output[i+1] = uint8_t((s >> 8) & 0xFF);
output[i+2] = uint8_t((s >> 16) & 0xFF);
}
}

inline void sound_convert_sint24m(const uint32_t samples, const int32_t* input, uint8_t* output)
{
for (uint32_t i=0,j=0; i<(samples*3); i+=3,j+=2)
{
const int32_t s = sound_convert_clamp24m(input[j+0],input[j+1]);
output[i+0] = uint8_t((s >> 0) & 0xFF);
output[i+1] = uint8_t((s >> 8) & 0xFF);
output[i+2] = uint8_t((s >> 16) & 0xFF);
}
}

inline void sound_convert_sint16s(const uint32_t samples, const int32_t* input, int16_t* output)
{
for (uint32_t i=0; i<(samples*2); ++i)
output[i] = sound_convert_clamp16(input[i]);
}

inline void sound_convert_sint16m(const uint32_t samples, const int32_t* input, int16_t* output)
{
for (uint32_t i=0; i<samples; ++i)
output[i] = sound_convert_clamp16m(input[(i*2)+0],input[(i*2)+1]);
}

inline void sound_convert_uint8s(const uint32_t samples, const int32_t* input, uint8_t* output)
{
for (uint32_t i=0; i<(samples*2); ++i)
output[i] = sound_convert_clamp8(input[i]);
}

inline void sound_convert_uint8m(const uint32_t samples, const int32_t* input, uint8_t* output)
{
for (uint32_t i=0; i<samples; ++i)
output[i] = sound_convert_clamp8m(input[(i*2)+0],input[(i*2)+1]);
}
50 changes: 50 additions & 0 deletions shared/wav_file.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// wav_file.cpp
// WAV file output

#include "wav_file.h"
//#include <cstdio> // FILE, std::fwrite, std::ftell, std::fseek

static inline void headfc(uint8_t* head, int pos, const char* fc) { for (int i=0;i<4;++i) head[pos+i] = fc[i]; }
static inline void head16( uint8_t* head, int pos, uint16_t v) { for (int i=0;i<2;++i) head[pos+i] = (v>>(8*i))&0xFF; }
static inline void head32( uint8_t* head, int pos, uint32_t v) { for (int i=0;i<4;++i) head[pos+i] = (v>>(8*i))&0xFF; }

bool wave_file_begin(FILE* f, uint32_t samplerate, uint16_t channels, uint16_t bits)
{
uint8_t head[0x2C];
// RIFF master chunk
headfc(head,0x00,"RIFF");
head32(head,0x04,0); // (file size - 12)
headfc(head,0x08,"WAVE");
// fmt chunk
headfc(head,0x0C,"fmt ");
head32(head,0x10,16); // fmt chunk size
head16(head,0x14,0x01); // PCM data
head16(head,0x16,channels);
head32(head,0x18,samplerate);
head32(head,0x1C,samplerate * channels * (bits/8));
head16(head,0x20,channels * (bits/8));
head16(head,0x22,bits);
// data chunk
headfc(head,0x24,"data");
head32(head,0x28,0); // data chunk size
return 0x2C == std::fwrite(head,1,0x2C,f);
}

bool wave_file_finish(FILE* f)
{
uint8_t head[4];
bool result = true;
long fsize = std::ftell(f);
if (fsize & 1) { result &= (0 == std::fputc(0,f)); ++fsize; } // padding byte needed for odd data chunk size
// RIFF chunk size
result &= (0 == std::fseek(f,0x04,SEEK_SET));
head32(head,0,fsize-12);
result &= (4 == std::fwrite(head,1,4,f));
// data chunk size
result &= (0 == std::fseek(f,0x28,SEEK_SET));
head32(head,0,fsize-(0x2C));
result &= (4 == std::fwrite(head,1,4,f));
// return to end of file
std::fseek(f,0,SEEK_END);
return result;
}
17 changes: 17 additions & 0 deletions shared/wav_file.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once
// shared/wav_file.h
// WAV file output

#include <cstdint>
#include <cstdio> // FILE

// 1. Open FILE.
// 2. Write header:
bool wave_file_begin(FILE* f, uint32_t samplerate, uint16_t channels, uint16_t bits);
// 3. Write sample data to FILE.
// 4. Finish FILE by updating header with data sizes:
bool wave_file_finish(FILE* f);
// 5. Close FILE.

// This creates a minimal RIFF WAVE (.WAV) file with no extra data fields.
// The total file size must be less than 2GB or the header field sizes will be incorrect.

0 comments on commit 15cd871

Please sign in to comment.