public unsafe static void Save(Song song, string filename, int sampleRate) { var advance = true; var tempoCounter = 0; var playPattern = 0; var playNote = 0; var speed = song.Speed; var wavBytes = new List <byte>(); var apuIndex = NesApu.APU_WAV_EXPORT; var dmcCallback = new NesApu.DmcReadDelegate(NesApu.DmcReadCallback); NesApu.NesApuInit(apuIndex, sampleRate, dmcCallback); NesApu.Reset(apuIndex); var channels = new ChannelState[5] { new SquareChannelState(apuIndex, 0), new SquareChannelState(apuIndex, 1), new TriangleChannelState(apuIndex, 2), new NoiseChannelState(apuIndex, 3), new DPCMChannelState(apuIndex, 4) }; for (int i = 0; i < 5; i++) { NesApu.NesApuEnableChannel(apuIndex, i, 1); } while (true) { // Advance to next note. if (advance) { foreach (var channel in channels) { channel.ProcessEffects(song, ref playPattern, ref playNote, ref speed, false); } foreach (var channel in channels) { channel.Advance(song, playPattern, playNote); } advance = false; } // Update envelopes + APU registers. foreach (var channel in channels) { channel.UpdateEnvelopes(); channel.UpdateAPU(); } NesApu.NesApuEndFrame(apuIndex); int numTotalSamples = NesApu.NesApuSamplesAvailable(apuIndex); byte[] samples = new byte[numTotalSamples * 2]; fixed(byte *ptr = &samples[0]) { NesApu.NesApuReadSamples(apuIndex, new IntPtr(ptr), numTotalSamples); } wavBytes.AddRange(samples); int dummy1 = 0; if (!PlayerBase.AdvanceTempo(song, speed, LoopMode.None, ref tempoCounter, ref playPattern, ref playNote, ref dummy1, ref advance)) { break; } } using (var file = new FileStream(filename, FileMode.Create)) { var header = new WaveHeader(); // RIFF WAVE Header header.chunkId[0] = (byte)'R'; header.chunkId[1] = (byte)'I'; header.chunkId[2] = (byte)'F'; header.chunkId[3] = (byte)'F'; header.format[0] = (byte)'W'; header.format[1] = (byte)'A'; header.format[2] = (byte)'V'; header.format[3] = (byte)'E'; // Format subchunk header.subChunk1Id[0] = (byte)'f'; header.subChunk1Id[1] = (byte)'m'; header.subChunk1Id[2] = (byte)'t'; header.subChunk1Id[3] = (byte)' '; header.audioFormat = 1; // FOR PCM header.numChannels = 1; // 1 for MONO, 2 for stereo header.sampleRate = sampleRate; // ie 44100 hertz, cd quality audio header.bitsPerSample = 16; // header.byteRate = header.sampleRate * header.numChannels * header.bitsPerSample / 8; header.blockAlign = (short)(header.numChannels * header.bitsPerSample / 8); // Data subchunk header.subChunk2Id[0] = (byte)'d'; header.subChunk2Id[1] = (byte)'a'; header.subChunk2Id[2] = (byte)'t'; header.subChunk2Id[3] = (byte)'a'; // All sizes for later: // chuckSize = 4 + (8 + subChunk1Size) + (8 + subChubk2Size) // subChunk1Size is constanst, i'm using 16 and staying with PCM // subChunk2Size = nSamples * nChannels * bitsPerSample/8 // Whenever a sample is added: // chunkSize += (nChannels * bitsPerSample/8) // subChunk2Size += (nChannels * bitsPerSample/8) header.subChunk1Size = 16; header.subChunk2Size = wavBytes.Count; header.chunkSize = 4 + (8 + header.subChunk1Size) + (8 + header.subChunk2Size); var headerBytes = new byte[sizeof(WaveHeader)]; Marshal.Copy(new IntPtr(&header), headerBytes, 0, headerBytes.Length); file.Write(headerBytes, 0, headerBytes.Length); file.Write(wavBytes.ToArray(), 0, wavBytes.Count); } }
unsafe void PlayerThread(object o) { var channels = new ChannelState[5] { new SquareChannelState(apuIndex, 0), new SquareChannelState(apuIndex, 1), new TriangleChannelState(apuIndex, 2), new NoiseChannelState(apuIndex, 3), new DPCMChannelState(apuIndex, 4) }; var startInfo = (SongPlayerStartInfo)o; var song = startInfo.song; bool advance = true; int tempoCounter = 0; int playPattern = 0; int playNote = 0; int speed = song.Speed; NesApu.Reset(apuIndex); if (startInfo.frame != 0) { foreach (var channel in channels) { channel.StartSeeking(); } while (playPattern * song.PatternLength + playNote != startInfo.frame) { foreach (var channel in channels) { channel.ProcessEffects(song, ref playPattern, ref playNote, ref speed); channel.Advance(song, playPattern, playNote); channel.UpdateEnvelopes(); channel.UpdateAPU(); } // Tempo/speed logic. tempoCounter += song.Tempo * 256 / 150; // NTSC if ((tempoCounter >> 8) == speed) { tempoCounter -= (speed << 8); if (++playNote == song.PatternLength) { playNote = 0; if (++playPattern == song.Length) { playPattern = 0; } } } } foreach (var channel in channels) { channel.StopSeeking(); } } var waitEvents = new WaitHandle[] { stopEvent, frameEvent }; while (true) { int idx = WaitHandle.WaitAny(waitEvents); if (idx == 0) { break; } // Advance to next note. if (advance) { foreach (var channel in channels) { channel.ProcessEffects(song, ref playPattern, ref playNote, ref speed); channel.Advance(song, playPattern, playNote); } advance = false; } // Update envelopes + APU registers. foreach (var channel in channels) { channel.UpdateEnvelopes(); channel.UpdateAPU(); } // Mute. for (int i = 0; i < 5; i++) { NesApu.NesApuEnableChannel(apuIndex, i, (channelMask & (1 << i))); } EndFrameAndQueueSamples(); // Tempo/speed logic. tempoCounter += song.Tempo * 256 / 150; // NTSC if ((tempoCounter >> 8) == speed) { tempoCounter -= (speed << 8); if (++playNote == song.PatternLength) { playNote = 0; if (loopMode != LoopMode.Pattern) { if (++playPattern == song.Length) { if (loopMode == LoopMode.None) { break; } playPattern = 0; } } } playFrame = playPattern * song.PatternLength + playNote; advance = true; } } xaudio2Stream.Stop(); while (sampleQueue.TryDequeue(out _)) { ; } }
unsafe void PlayerThread(object o) { var channels = new ChannelState[5] { new SquareChannelState(apuIndex, 0), new SquareChannelState(apuIndex, 1), new TriangleChannelState(apuIndex, 2), new NoiseChannelState(apuIndex, 3), new DPCMChannelState(apuIndex, 4) }; var startInfo = (SongPlayerStartInfo)o; var song = startInfo.song; bool advance = true; int tempoCounter = 0; int playPattern = 0; int playNote = 0; int speed = song.Speed; NesApu.Reset(apuIndex); if (startInfo.frame != 0) { foreach (var channel in channels) { channel.StartSeeking(); } while (playPattern * song.PatternLength + playNote != startInfo.frame) { foreach (var channel in channels) { channel.ProcessEffects(song, ref playPattern, ref playNote, ref speed); } foreach (var channel in channels) { channel.Advance(song, playPattern, playNote); channel.UpdateEnvelopes(); channel.UpdateAPU(); } int dummy1 = 0; bool dummy2 = false; if (!AdvanceTempo(song, speed, LoopMode.None, ref tempoCounter, ref playPattern, ref playNote, ref dummy1, ref dummy2)) { break; } } foreach (var channel in channels) { channel.StopSeeking(); } } var waitEvents = new WaitHandle[] { stopEvent, frameEvent }; while (true) { int idx = WaitHandle.WaitAny(waitEvents); if (idx == 0) { break; } // Advance to next note. if (advance) { // We process the effects before since one channel may have // a skip/jump and we need to process that first before advancing // the song. foreach (var channel in channels) { channel.ProcessEffects(song, ref playPattern, ref playNote, ref speed); } foreach (var channel in channels) { channel.Advance(song, playPattern, playNote); } advance = false; } // Update envelopes + APU registers. foreach (var channel in channels) { channel.UpdateEnvelopes(); channel.UpdateAPU(); } // Mute. for (int i = 0; i < 5; i++) { NesApu.NesApuEnableChannel(apuIndex, i, (channelMask & (1 << i))); } EndFrameAndQueueSamples(); if (!AdvanceTempo(song, speed, loopMode, ref tempoCounter, ref playPattern, ref playNote, ref playFrame, ref advance)) { break; } } audioStream.Stop(); while (sampleQueue.TryDequeue(out _)) { ; } }
unsafe void PlayerThread(object o) { var channels = new ChannelState[5] { new SquareChannelState(apuIndex, 0), new SquareChannelState(apuIndex, 1), new TriangleChannelState(apuIndex, 2), new NoiseChannelState(apuIndex, 3), new DPCMChannelState(apuIndex, 4) }; var activeChannel = -1; var waitEvents = new WaitHandle[] { stopEvent, frameEvent }; NesApu.Reset(apuIndex); for (int i = 0; i < 5; i++) { NesApu.NesApuEnableChannel(apuIndex, i, 0); } while (true) { int idx = WaitHandle.WaitAny(waitEvents); if (idx == 0) { break; } if (!noteQueue.IsEmpty) { PlayerNote lastNote = new PlayerNote(); while (noteQueue.TryDequeue(out PlayerNote note)) { lastNote = note; } activeChannel = lastNote.channel; if (activeChannel >= 0) { channels[activeChannel].PlayNote(lastNote.note); } for (int i = 0; i < 5; i++) { NesApu.NesApuEnableChannel(apuIndex, i, i == activeChannel ? 1 : 0); } } if (activeChannel >= 0) { channels[activeChannel].UpdateEnvelopes(); channels[activeChannel].UpdateAPU(); for (int i = 0; i < Envelope.Max; i++) { envelopeFrames[i] = channels[activeChannel].GetEnvelopeFrame(i); } } else { for (int i = 0; i < Envelope.Max; i++) { envelopeFrames[i] = 0; } foreach (var channel in channels) { channel.ClearNote(); } } EndFrameAndQueueSamples(); } xaudio2Stream.Stop(); while (sampleQueue.TryDequeue(out _)) { ; } }