예제 #1
0
        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);
            }
        }
예제 #2
0
        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 _))
            {
                ;
            }
        }
예제 #3
0
        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 _))
            {
                ;
            }
        }
예제 #4
0
        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 _))
            {
                ;
            }
        }