org.refcodes.audio.AbstractWavSampleWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of refcodes-audio Show documentation
Show all versions of refcodes-audio Show documentation
Artifact providing audio provessing functionality such as generating
sine waves or writing raw audio samples to WAV files.
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// /////////////////////////////////////////////////////////////////////////////
// This code is copyright (c) by Siegfried Steiner, Munich, Germany, distributed
// on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, and licen-
// sed under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// -----------------------------------------------------------------------------
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// -----------------------------------------------------------------------------
// Apache License, v2.0 ("http://www.apache.org/licenses/TEXT-2.0")
// -----------------------------------------------------------------------------
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////
package org.refcodes.audio;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import org.refcodes.numerical.NumericalUtility;
/**
* The {@link AbstractWavSampleWriter} provides a foundation means to write
* sound samples to a WAV file. Information on the WAV file format has been
* taken from the following article:
* "https://web.archive.org/web/20120113025807/http://technology.niagarac.on.ca:80/courses/ctec1631/WavFileFormat.html".
*
* @param The {@link SoundSample} (sub-)type on which the
* {@link SampleWriter} implementation is to operate on.
* @param The {@link WavSampleWriter} implementing this
* {@link AbstractWavSampleWriter}.
*/
public abstract class AbstractWavSampleWriter> implements WavSampleWriter {
// /////////////////////////////////////////////////////////////////////////
// CONSTANTS:
// /////////////////////////////////////////////////////////////////////////
public static String RIFF = "RIFF";
public static String WAVE = "WAVE";
public static String FORMAT = "fmt ";
public static String DATA = "data";
protected static final long MAX_16_BIT = 65535;
protected static final long MAX_8_BIT = 255;
// /////////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////////
protected BufferedOutputStream _outputStream;
protected File _file = null;
protected BitsPerSample _bitsPerSample = BitsPerSample.HIGH_RES;
// /////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////////
/**
* Constructs the {@link AbstractWavSampleWriter} for writing sound samples
* to a WAV file or stream.
*
* @param aFile The {@link File} where to write the CSV records to.
*
* @throws FileNotFoundException If the given file object does not denote an
* existing, writable regular file and a new regular file of that
* name cannot be created, or if some other error occurs while
* opening or creating the file.
*/
public AbstractWavSampleWriter( File aFile ) throws FileNotFoundException {
this( new FileOutputStream( aFile ) );
_file = aFile;
}
/**
* Constructs the {@link AbstractWavSampleWriter} for writing sound samples
* to a WAV file or stream.
*
* @param aOutputStream The {@link OutputStream} where to write the CSV
* records to.
*/
public AbstractWavSampleWriter( OutputStream aOutputStream ) {
_outputStream = aOutputStream instanceof BufferedOutputStream ? (BufferedOutputStream) aOutputStream : new BufferedOutputStream( aOutputStream );
}
// /////////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
@Override
public void setBitsPerSample( BitsPerSample aBitsPerSample ) {
_bitsPerSample = aBitsPerSample;
}
/**
* {@inheritDoc}
*/
@Override
public BitsPerSample getBitsPerSample() {
return _bitsPerSample;
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
_outputStream.flush();
_outputStream.close();
if ( _file != null ) {
final long size = _file.length();
try ( RandomAccessFile theRndFile = new RandomAccessFile( _file, "rw" ) ) {
theRndFile.seek( 4 );
byte[] theSizeAfter = NumericalUtility.toLittleEndianBytes( size - 8, 4 );
theRndFile.write( theSizeAfter );
theRndFile.seek( 40 );
theSizeAfter = NumericalUtility.toLittleEndianBytes( size - 28, 4 );
theRndFile.write( theSizeAfter );
}
}
}
// /////////////////////////////////////////////////////////////////////////
// HOOKS:
// /////////////////////////////////////////////////////////////////////////
protected long toWavSample( double eSampleData ) {
return switch ( _bitsPerSample ) {
case HIGH_RES -> (long) ( eSampleData * ( MAX_16_BIT / 2 ) ); // 16 Bit WAV: UNSIGNED
case LOW_RES -> (long) ( ( eSampleData + 1 ) * ( MAX_8_BIT / 2 ) ); // 8 Bit WAV: SIGEND
};
}
// /////////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////////
protected void writeHeader( int aSamplingRate, int aChannelNumber ) throws IOException {
// RIFF Chunk (12 bytes in length total):
_outputStream.write( RIFF.getBytes( StandardCharsets.US_ASCII ) ); // Offset 0: Big endian, 4 Bytes
_outputStream.write( NumericalUtility.toLittleEndianBytes( 0, 4 ) ); // Offset 4: Little endian, 4 Bytes, Total Length Of Package To Follow
_outputStream.write( WAVE.getBytes( StandardCharsets.US_ASCII ) ); // Offset 8: Big endian, 4 Bytes
// FORMAT Chunk (24 bytes in length total):
_outputStream.write( FORMAT.getBytes( StandardCharsets.US_ASCII ) ); // Offset 12: Big endian, 4 Bytes
_outputStream.write( NumericalUtility.toLittleEndianBytes( 16, 4 ) ); // Offset 16: Little endian, 4 Bytes, Length Of FORMAT Chunk, Binary, always 0x10
_outputStream.write( NumericalUtility.toLittleEndianBytes( 1, 2 ) ); // Offset 20: Little endian, 2 Bytes, Always 0x01
_outputStream.write( NumericalUtility.toLittleEndianBytes( aChannelNumber, 2 ) ); // Offset 22: Little endian, 2 Bytes, Channel Numbers
_outputStream.write( NumericalUtility.toLittleEndianBytes( aSamplingRate, 4 ) ); // Offset 24: Little endian, 4 Bytes, Sample Rate
_outputStream.write( NumericalUtility.toLittleEndianBytes( aSamplingRate * aChannelNumber * _bitsPerSample.getByteCount(), 4 ) ); // Offset 28: Little endian, 4 Bytes, Bytes Per Second
_outputStream.write( NumericalUtility.toLittleEndianBytes( _bitsPerSample.getByteCount() * aChannelNumber, 2 ) ); // Offset 32: Little endian, 2 Bytes, Block Align: Bytes Per Sample: 1=8 bit Mono, 2=8 bit Stereo or 16 bit Mono, 4=16 bit Stereo
_outputStream.write( NumericalUtility.toLittleEndianBytes( _bitsPerSample.getBitCount() * aChannelNumber, 2 ) ); // Offset 34: Little endian, 2 Bytes, Bits Per Sample
// DATA Chunk:
_outputStream.write( DATA.getBytes( StandardCharsets.US_ASCII ) ); // Offset 36: Big endian, 4 Bytes,
_outputStream.write( NumericalUtility.toLittleEndianBytes( 0, 4 ) ); // Offset 40: Little endian, 4 Bytes, Length Of Data To Follow
// Offset 44: The actual sound data |-->
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy