All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.refcodes.audio.AbstractWavSampleWriter Maven / Gradle / Ivy

Go to download

Artifact providing audio provessing functionality such as generating sine waves or writing raw audio samples to WAV files.

The newest version!
// /////////////////////////////////////////////////////////////////////////////
// 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 |-->
	}
}