2012-05-11 17:00:44 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.IO ;
2014-07-03 17:13:09 +00:00
using BizHawk.Common.IOExtensions ;
2013-11-04 01:39:19 +00:00
using BizHawk.Emulation.Common ;
2013-10-27 22:07:40 +00:00
2013-11-03 03:54:37 +00:00
namespace BizHawk.Client.EmuHawk
2012-05-11 17:00:44 +00:00
{
/// <summary>
/// writes MS standard riff files containing uncompressed PCM wav data
/// supports 16 bit signed data only
/// </summary>
public class WavWriter : IDisposable
{
/// <summary>
/// underlying file being written to
/// </summary>
BinaryWriter file ;
/// <summary>
/// sequence of files to write to (split on 32 bit limit)
/// </summary>
IEnumerator < Stream > filechain ;
/// <summary>
/// samplerate in HZ
/// </summary>
int samplerate ;
/// <summary>
/// number of audio channels
/// </summary>
int numchannels ;
/// <summary>
/// number of bytes of PCM data written to current file
/// </summary>
UInt64 numbytes ;
/// <summary>
/// number of bytes after which a file split should be made
/// </summary>
const UInt64 splitpoint = 2 * 1000 * 1000 * 1000 ;
/// <summary>
/// write riff headers to current file
/// </summary>
void writeheaders ( )
{
file . Write ( Encoding . ASCII . GetBytes ( "RIFF" ) ) ; // ChunkID
2014-07-22 00:21:51 +00:00
file . Write ( ( uint ) 0 ) ; // ChunkSize
2012-05-11 17:00:44 +00:00
file . Write ( Encoding . ASCII . GetBytes ( "WAVE" ) ) ; // Format
file . Write ( Encoding . ASCII . GetBytes ( "fmt " ) ) ; // SubchunkID
2014-07-22 00:21:51 +00:00
file . Write ( ( uint ) 16 ) ; // SubchunkSize
file . Write ( ( ushort ) 1 ) ; // AudioFormat (PCM)
file . Write ( ( ushort ) numchannels ) ; // NumChannels
file . Write ( ( uint ) samplerate ) ; // SampleRate
file . Write ( ( uint ) ( samplerate * numchannels * 2 ) ) ; // ByteRate
file . Write ( ( ushort ) ( numchannels * 2 ) ) ; // BlockAlign
file . Write ( ( ushort ) 16 ) ; // BitsPerSample
2012-05-11 17:00:44 +00:00
file . Write ( Encoding . ASCII . GetBytes ( "data" ) ) ; // SubchunkID
2014-07-22 00:21:51 +00:00
file . Write ( ( uint ) 0 ) ; // SubchunkSize
2012-05-11 17:00:44 +00:00
}
/// <summary>
/// seek back to beginning of file and fix header sizes (if possible)
/// </summary>
void finalizeheaders ( )
{
if ( numbytes + 36 > = 0x100000000 )
// passed 4G limit, nothing to be done
return ;
try
{
file . Seek ( 4 , SeekOrigin . Begin ) ;
2014-07-22 00:21:51 +00:00
file . Write ( ( uint ) ( 36 + numbytes ) ) ;
2012-05-11 17:00:44 +00:00
file . Seek ( 40 , SeekOrigin . Begin ) ;
2014-07-22 00:21:51 +00:00
file . Write ( ( uint ) ( numbytes ) ) ;
2012-05-11 17:00:44 +00:00
}
catch ( NotSupportedException )
{ // unseekable; oh well
}
}
/// <summary>
/// close current underlying stream
/// </summary>
2014-07-22 00:21:51 +00:00
void closecurrent ( )
2012-05-11 17:00:44 +00:00
{
if ( file ! = null )
{
finalizeheaders ( ) ;
file . Close ( ) ;
file . Dispose ( ) ;
}
file = null ;
}
/// <summary>
/// open a new underlying stream
/// </summary>
/// <param name="next"></param>
2014-07-22 00:21:51 +00:00
void opencurrent ( Stream next )
2012-05-11 17:00:44 +00:00
{
file = new BinaryWriter ( next , Encoding . ASCII ) ;
numbytes = 0 ;
writeheaders ( ) ;
}
/// <summary>
/// write samples to file
/// </summary>
/// <param name="samples">samples to write; should contain one for each channel</param>
public void writesamples ( short [ ] samples )
{
file . Write ( samples ) ;
2014-07-22 00:21:51 +00:00
numbytes + = ( ulong ) ( samples . Length * sizeof ( short ) ) ;
2012-05-11 17:00:44 +00:00
// try splitting if we can
if ( numbytes > = splitpoint & & filechain ! = null )
{
if ( ! filechain . MoveNext ( ) )
{ // out of files, just keep on writing to this one
filechain = null ;
}
else
{
Stream next = filechain . Current ;
closecurrent ( ) ;
opencurrent ( next ) ;
}
}
}
public void Dispose ( )
{
Close ( ) ;
}
/// <summary>
/// finishes writing
/// </summary>
public void Close ( )
{
closecurrent ( ) ;
}
/// <summary>
/// checks sampling rate, number of channels for validity
/// </summary>
void checkargs ( )
{
if ( samplerate < 1 | | numchannels < 1 )
throw new ArgumentException ( "Bad samplerate/numchannels" ) ;
}
/// <summary>
/// initializes WavWriter with a single output stream
/// no attempt is made to split
/// </summary>
/// <param name="s">WavWriter now owns this stream</param>
/// <param name="samplerate">sampling rate in HZ</param>
/// <param name="numchannels">number of audio channels</param>
public WavWriter ( Stream s , int samplerate , int numchannels )
{
this . samplerate = samplerate ;
this . numchannels = numchannels ;
filechain = null ;
checkargs ( ) ;
opencurrent ( s ) ;
}
/// <summary>
/// initializes WavWriter with an enumeration of Streams
/// one is consumed every time 2G is hit
/// if the enumerator runs out before the audio stream does, the last file could be >2G
/// </summary>
/// <param name="ss">WavWriter now owns any of these streams that it enumerates</param>
/// <param name="samplerate">sampling rate in HZ</param>
/// <param name="numchannels">number of audio channels</param>
public WavWriter ( IEnumerator < Stream > ss , int samplerate , int numchannels )
{
this . samplerate = samplerate ;
this . numchannels = numchannels ;
checkargs ( ) ;
filechain = ss ;
// advance to first
if ( ! filechain . MoveNext ( ) )
throw new ArgumentException ( "Iterator was empty!" ) ;
opencurrent ( ss . Current ) ;
2014-07-22 00:21:51 +00:00
}
2012-05-11 17:00:44 +00:00
}
/// <summary>
/// slim wrapper on WavWriter that implements IVideoWriter (discards all video!)
/// </summary>
2014-10-10 18:09:00 +00:00
[VideoWriter("wave", "WAV writer", "Writes a series of standard RIFF wav files containing uncompressed audio. Does not write video. Splits every 2G.")]
2012-05-11 17:00:44 +00:00
public class WavWriterV : IVideoWriter
{
public void SetVideoCodecToken ( IDisposable token ) { }
public void AddFrame ( IVideoProvider source ) { }
public void SetMovieParameters ( int fpsnum , int fpsden ) { }
public void SetVideoParameters ( int width , int height ) { }
2014-06-18 02:28:07 +00:00
public void SetFrame ( int frame ) { }
2012-05-11 17:00:44 +00:00
2016-03-05 23:19:12 +00:00
public bool UsesAudio { get { return true ; } }
public bool UsesVideo { get { return false ; } }
2012-05-11 17:00:44 +00:00
class WavWriterVToken : IDisposable
{
public void Dispose ( ) { }
}
2012-06-16 16:51:47 +00:00
public IDisposable AcquireVideoCodecToken ( System . Windows . Forms . IWin32Window hwnd )
2012-05-11 17:00:44 +00:00
{
// don't care
return new WavWriterVToken ( ) ;
}
public void SetAudioParameters ( int sampleRate , int channels , int bits )
{
this . sampleRate = sampleRate ;
this . channels = channels ;
if ( bits ! = 16 )
throw new ArgumentException ( "Only support 16bit audio!" ) ;
}
public void SetMetaData ( string gameName , string authors , ulong lengthMS , ulong rerecords )
{
// not implemented
}
public void Dispose ( )
{
if ( wavwriter ! = null )
wavwriter . Dispose ( ) ;
}
WavWriter wavwriter = null ;
int sampleRate = 0 ;
int channels = 0 ;
/// <summary>
/// create a simple wav stream iterator
/// </summary>
/// <param name="template"></param>
/// <returns></returns>
static IEnumerator < Stream > CreateStreamIterator ( string template )
{
string dir = Path . GetDirectoryName ( template ) ;
string baseName = Path . GetFileNameWithoutExtension ( template ) ;
string ext = Path . GetExtension ( template ) ;
yield return new FileStream ( template , FileMode . Create ) ;
int counter = 1 ;
while ( true )
{
2014-07-22 00:21:51 +00:00
yield return new FileStream ( Path . Combine ( dir , baseName ) + "_" + counter + ext , FileMode . Create ) ;
2012-05-11 17:00:44 +00:00
counter + + ;
}
}
public void OpenFile ( string baseName )
{
2012-06-10 00:53:19 +00:00
wavwriter = new WavWriter ( CreateStreamIterator ( baseName ) , sampleRate , channels ) ;
2012-05-11 17:00:44 +00:00
}
public void CloseFile ( )
{
wavwriter . Close ( ) ;
wavwriter . Dispose ( ) ;
wavwriter = null ;
}
public void AddSamples ( short [ ] samples )
{
wavwriter . writesamples ( samples ) ;
}
2012-06-13 19:50:50 +00:00
public string DesiredExtension ( )
{
return "wav" ;
}
2012-07-23 00:33:30 +00:00
public void SetDefaultVideoCodecToken ( )
{
// don't use codec tokens, so don't care
}
2012-05-11 17:00:44 +00:00
}
}