mirror of
https://github.com/safiire/n65.git
synced 2024-06-11 07:29:27 +00:00
Update for Ruby 2.4
This commit is contained in:
parent
2293fd2251
commit
a48ce496e8
|
@ -1,202 +0,0 @@
|
||||||
;------------------------------------------------------------------------------
|
|
||||||
; An NES music engine that understands the binary stream outputted from my
|
|
||||||
; MIDI converter :)
|
|
||||||
;;;;
|
|
||||||
; Create an iNES header
|
|
||||||
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
|
|
||||||
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
; Include all the symbols in the nes library
|
|
||||||
.inc <nes.sym>
|
|
||||||
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
; Let's put a data structure to control the sound engine in the zero page
|
|
||||||
.org $0000
|
|
||||||
.scope sound_engine
|
|
||||||
; Where we are reading from ROM
|
|
||||||
.space stream_read_ptr_lo 1
|
|
||||||
.space stream_read_ptr_hi 1
|
|
||||||
|
|
||||||
; Where we are writing in the APU
|
|
||||||
.space stream_write_ptr_lo 1
|
|
||||||
.space stream_write_ptr_hi 1
|
|
||||||
.space delta 1
|
|
||||||
.
|
|
||||||
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
; Open the prog section bank 0
|
|
||||||
.segment prog 0
|
|
||||||
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
; Setup the interrupt vectors
|
|
||||||
.org $FFFA
|
|
||||||
.dw vblank
|
|
||||||
.dw main
|
|
||||||
.dw irq
|
|
||||||
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
; Here is our code entry point, which we'll call main.
|
|
||||||
.org $C000
|
|
||||||
.scope main
|
|
||||||
; Disable interrupts and decimal flag
|
|
||||||
sei
|
|
||||||
cld
|
|
||||||
|
|
||||||
; Wait for 2 vblanks
|
|
||||||
wait_vb1:
|
|
||||||
lda nes.ppu.status
|
|
||||||
bpl wait_vb1
|
|
||||||
wait_vb2:
|
|
||||||
lda nes.ppu.status
|
|
||||||
bpl wait_vb2
|
|
||||||
|
|
||||||
; Now we want to initialize the hardware to a known state
|
|
||||||
lda #%00
|
|
||||||
ldx #$00
|
|
||||||
clear_segments:
|
|
||||||
sta $0, x
|
|
||||||
sta $100, x
|
|
||||||
sta $200, x
|
|
||||||
sta $300, x
|
|
||||||
sta $400, x
|
|
||||||
sta $500, x
|
|
||||||
sta $600, x
|
|
||||||
sta $700, x
|
|
||||||
inx
|
|
||||||
bne clear_segments
|
|
||||||
|
|
||||||
; Reset the stack pointer
|
|
||||||
ldx #$FF
|
|
||||||
txs
|
|
||||||
|
|
||||||
; Disable all graphics and vblank nmi
|
|
||||||
lda #$00
|
|
||||||
sta nes.ppu.control
|
|
||||||
sta nes.ppu.mask
|
|
||||||
|
|
||||||
jsr init_sound
|
|
||||||
|
|
||||||
; Resume interrupts and NMI and loop here forever
|
|
||||||
lda #%10000000
|
|
||||||
sta nes.ppu.control
|
|
||||||
cli
|
|
||||||
forever:
|
|
||||||
jmp forever
|
|
||||||
.
|
|
||||||
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
; Initialize the APU to enable Pulse1
|
|
||||||
.scope init_sound
|
|
||||||
lda #$00
|
|
||||||
ldy #$00
|
|
||||||
clear_apu:
|
|
||||||
sta nes.apu, y
|
|
||||||
iny
|
|
||||||
cpy #$10
|
|
||||||
bne clear_apu
|
|
||||||
|
|
||||||
lda #>music_buffer
|
|
||||||
ldx #<music_buffer
|
|
||||||
sta sound_engine.stream_read_ptr_hi
|
|
||||||
stx sound_engine.stream_read_ptr_lo
|
|
||||||
|
|
||||||
lda #$40
|
|
||||||
ldx #$00
|
|
||||||
sta sound_engine.stream_write_ptr_hi
|
|
||||||
stx sound_engine.stream_write_ptr_lo
|
|
||||||
|
|
||||||
lda #$01
|
|
||||||
sta sound_engine.delta
|
|
||||||
|
|
||||||
lda #$01
|
|
||||||
sta nes.apu.channel_enable
|
|
||||||
rts
|
|
||||||
.
|
|
||||||
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
; VBlank reads our music buffer
|
|
||||||
.scope vblank
|
|
||||||
; Backup our registers
|
|
||||||
pha
|
|
||||||
txa
|
|
||||||
pha
|
|
||||||
tya
|
|
||||||
pha
|
|
||||||
|
|
||||||
jsr sound_engine_driver
|
|
||||||
|
|
||||||
; Restore the registers
|
|
||||||
pla
|
|
||||||
tay
|
|
||||||
pla
|
|
||||||
tax
|
|
||||||
pla
|
|
||||||
rti
|
|
||||||
.
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
; Sound driver that updates the APU registers
|
|
||||||
; via a stream of APU write commands
|
|
||||||
.scope sound_engine_driver
|
|
||||||
dec sound_engine.delta
|
|
||||||
bne done
|
|
||||||
|
|
||||||
read_event:
|
|
||||||
; Load the new delta from the stream
|
|
||||||
ldy #$00
|
|
||||||
lda (sound_engine.stream_read_ptr_lo), Y
|
|
||||||
sta sound_engine.delta
|
|
||||||
|
|
||||||
; Read pulse1 control register value
|
|
||||||
ldy #$01
|
|
||||||
lda (sound_engine.stream_read_ptr_lo), Y
|
|
||||||
sta nes.apu.pulse1.control
|
|
||||||
|
|
||||||
; Read the value for pulse1.ft
|
|
||||||
ldy #$02
|
|
||||||
lda (sound_engine.stream_read_ptr_lo), Y
|
|
||||||
sta nes.apu.pulse1.ft
|
|
||||||
|
|
||||||
|
|
||||||
; Read the value for pulse1.ct
|
|
||||||
ldy #$03
|
|
||||||
lda (sound_engine.stream_read_ptr_lo), Y
|
|
||||||
sta nes.apu.pulse1.ct
|
|
||||||
|
|
||||||
; Advance the 16-bit stream pointer by number of bytes read
|
|
||||||
lda sound_engine.stream_read_ptr_lo
|
|
||||||
clc
|
|
||||||
adc #$04
|
|
||||||
sta sound_engine.stream_read_ptr_lo
|
|
||||||
bcc done
|
|
||||||
inc sound_engine.stream_read_ptr_hi
|
|
||||||
|
|
||||||
; If the very next event is 0 delta away, do it now too
|
|
||||||
; Read the value for pulse1.ct
|
|
||||||
ldy #$04
|
|
||||||
lda (sound_engine.stream_read_ptr_lo), Y
|
|
||||||
beq read_event
|
|
||||||
|
|
||||||
done:
|
|
||||||
rts
|
|
||||||
.
|
|
||||||
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
; IRQ Does nothing
|
|
||||||
.scope irq
|
|
||||||
rti
|
|
||||||
.
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
; Include the music buffer stream
|
|
||||||
.org $D000
|
|
||||||
music_buffer:
|
|
||||||
.incbin "data.mus"
|
|
|
@ -52,7 +52,7 @@ module N65
|
||||||
|
|
||||||
## Try to execute it now, or setup the promise to return
|
## Try to execute it now, or setup the promise to return
|
||||||
case @value
|
case @value
|
||||||
when Fixnum
|
when Integer
|
||||||
bytes = [@value & 0xFFFF].pack('S').bytes
|
bytes = [@value & 0xFFFF].pack('S').bytes
|
||||||
assembler.write_memory(bytes)
|
assembler.write_memory(bytes)
|
||||||
when String
|
when String
|
||||||
|
|
|
@ -57,7 +57,7 @@ module N65
|
||||||
:example => 'AAA $FF, Y',
|
:example => 'AAA $FF, Y',
|
||||||
:display => '%s $%.2X, Y',
|
:display => '%s $%.2X, Y',
|
||||||
:regex => /^#{Mnemonic}\s+#{Num8}\s?,\s?#{YReg}$/,
|
:regex => /^#{Mnemonic}\s+#{Num8}\s?,\s?#{YReg}$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg} zp$/
|
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}\s+zp$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:absolute => {
|
:absolute => {
|
||||||
|
@ -220,7 +220,7 @@ module N65
|
||||||
end
|
end
|
||||||
|
|
||||||
case @arg
|
case @arg
|
||||||
when Fixnum, NilClass
|
when Integer, NilClass
|
||||||
assembler.write_memory(emit_bytes)
|
assembler.write_memory(emit_bytes)
|
||||||
when String
|
when String
|
||||||
begin
|
begin
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module N65
|
module N65
|
||||||
VERSION = "0.5.0"
|
VERSION ||= "1.0.0"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
build: convert
|
|
||||||
clang++ source/*.cpp -I include -o convert
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,93 +0,0 @@
|
||||||
#ifndef MIDI_EVENT_H
|
|
||||||
#define MIDI_EVENT_H
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include "helpers.h"
|
|
||||||
|
|
||||||
namespace Midi {
|
|
||||||
|
|
||||||
enum MidiMessageType {Midi = 0, MidiNoteOff = 0x8, MidiNoteOn, MidiPolyAftertouch,
|
|
||||||
MidiControlChange, MidiProgramChange, MidiChannelAftertouch,
|
|
||||||
MidiPitchWheel, MidiMetaEvent = 0xFF, MidiSysex1 = 0xF0, MidiSysex2 = 0xF7};
|
|
||||||
|
|
||||||
|
|
||||||
enum MidiMetaType {MetaSequenceNumber = 0x0, MetaTextEvent = 0x1, MetaCopyright = 0x2,
|
|
||||||
MetaTrackName = 0x3, MetaInstrumentName = 0x4, MetaLyricText = 0x5,
|
|
||||||
MetaMarkerText = 0x6, MetaCuePoint = 0x7, MetaChannelPrefixAssignment = 0x20,
|
|
||||||
MetaEndOfTrack = 0x2F, MetaTempoSetting = 0x51, MetaSMPTEOffset = 0x54,
|
|
||||||
MetaTimeSignature = 0x58, MetaKeySignature = 0x59, MetaSequenceSpecific = 0x7F};
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Spec:
|
|
||||||
* MidiTrackEvent = <delta_time (Variable)> + <MidiEvent> | <MetaEvent> | <SysexEvent>
|
|
||||||
* MidiEvent = <MidiMessageType (4 bits)> + <channel (4 bits)> + <parameter1 (1 byte)> <parameter2 (1 byte)>
|
|
||||||
* MetaEvent = 0xFF + <MidiMetaType> + <size (Variable)> + <data>
|
|
||||||
* SysexEvent = 0xF0 + <data> + 0xF7 -or-
|
|
||||||
* SysexEvent = 0xF7 + <data> 0xF7
|
|
||||||
***/
|
|
||||||
|
|
||||||
class Event {
|
|
||||||
private:
|
|
||||||
unsigned int m_delta;
|
|
||||||
unsigned int m_size;
|
|
||||||
unsigned int m_data_size;
|
|
||||||
unsigned char m_status;
|
|
||||||
unsigned char m_meta_type;
|
|
||||||
unsigned char m_parameter1;
|
|
||||||
unsigned char m_parameter2;
|
|
||||||
void *m_data;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Event();
|
|
||||||
Event(int delta, int status, int parameter1, int parameter2);
|
|
||||||
void init_midi(int delta, int status, int parameter1, int parameter2);
|
|
||||||
void init_from_file(FILE *fp, unsigned char last_status);
|
|
||||||
~Event();
|
|
||||||
|
|
||||||
// Some easy inline functions
|
|
||||||
static bool is_status_byte(unsigned char byte) { return ((byte & 0x80) >> 7) == 1; }
|
|
||||||
unsigned int delta() const { return m_delta; }
|
|
||||||
unsigned char status() const { return m_status; }
|
|
||||||
unsigned int bytes_read() const { return m_size; }
|
|
||||||
unsigned char parameter1() const { return m_parameter1; }
|
|
||||||
unsigned char parameter2() const { return m_parameter2; }
|
|
||||||
int message_type() const { return (m_status &0xF0) >> 4; }
|
|
||||||
int channel() const { return (m_status &0xF); }
|
|
||||||
void *sysex_data() const { return m_data; }
|
|
||||||
|
|
||||||
|
|
||||||
void print_yaml(){
|
|
||||||
printf(" - :delta: 0x%X\n", m_delta);
|
|
||||||
printf(" :status: 0x%X\n", m_status);
|
|
||||||
|
|
||||||
switch(m_status){
|
|
||||||
case 0xFF:
|
|
||||||
printf(" :meta_type: 0x%X\n", m_meta_type);
|
|
||||||
printf(" :meta_data_size: 0x%X\n", m_data_size);
|
|
||||||
break;
|
|
||||||
case 0xF0:
|
|
||||||
case 0xF7:
|
|
||||||
printf(" :sysex_data_size: %d\n", m_data_size);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
printf(" :parameter1: 0x%X\n", m_parameter1);
|
|
||||||
printf(" :parameter2: 0x%X\n", m_parameter2);
|
|
||||||
}
|
|
||||||
/* Printing out the actual data as a string confuses the YAML parser
|
|
||||||
* So let's just not do that.
|
|
||||||
if(m_data){
|
|
||||||
printf(" :data: \"");
|
|
||||||
|
|
||||||
for(int i = 0; i < m_data_size; i++){
|
|
||||||
printf("%c", ((unsigned char*)m_data)[i]);
|
|
||||||
}
|
|
||||||
printf("\"\n");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,57 +0,0 @@
|
||||||
#ifndef MIDI_FILE_H
|
|
||||||
#define MIDI_FILE_H
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include "track.h"
|
|
||||||
|
|
||||||
namespace Midi {
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Spec:
|
|
||||||
* MidiFile = <midi_header_t> + <MidiTrack> [+ <MidiTrack> ...]
|
|
||||||
*
|
|
||||||
* midi_header_t = "MThd" + <size (4 bytes)> + <format (2 bytes)> + <track_count (2 bytes)> + <ticks_per_quarter_note (2 bytes)>
|
|
||||||
*
|
|
||||||
***/
|
|
||||||
|
|
||||||
typedef struct midi_header_t {
|
|
||||||
char cookie[4];
|
|
||||||
unsigned int size;
|
|
||||||
unsigned short format;
|
|
||||||
unsigned short track_count;
|
|
||||||
unsigned short ticks_per_quarter_note;
|
|
||||||
} __attribute__((packed)) midi_header_t;
|
|
||||||
|
|
||||||
|
|
||||||
class File {
|
|
||||||
private:
|
|
||||||
midi_header_t m_header;
|
|
||||||
|
|
||||||
public:
|
|
||||||
File(void);
|
|
||||||
~File(void);
|
|
||||||
std::vector<Track *> m_tracks;
|
|
||||||
void init_from_file(const char *filename);
|
|
||||||
|
|
||||||
void print_yaml(){
|
|
||||||
printf("---\n");
|
|
||||||
printf(":midi_file:\n");
|
|
||||||
printf(" :header: Mthd\n");
|
|
||||||
printf(" :size: %u\n", m_header.size);
|
|
||||||
printf(" :format: %u\n", m_header.format);
|
|
||||||
printf(" :track_count: %u\n", m_header.track_count);
|
|
||||||
printf(" :ticks_per_quarter_note: %u\n", m_header.ticks_per_quarter_note);
|
|
||||||
printf(" :tracks:\n");
|
|
||||||
|
|
||||||
for(int i = 0; i < m_tracks.size(); i++){
|
|
||||||
if(i == 0){
|
|
||||||
m_tracks[i]->print_yaml();
|
|
||||||
}else{
|
|
||||||
m_tracks[i]->print_yaml();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,14 +0,0 @@
|
||||||
#ifndef MIDI_HELPERS_H
|
|
||||||
#define MIDI_HELPERS_H
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
namespace Midi {
|
|
||||||
|
|
||||||
unsigned int read_variable_length(FILE *fp, unsigned int *value_size);
|
|
||||||
short swap_endian_16(short big_endian);
|
|
||||||
int swap_endian_32(int big_endian);
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,45 +0,0 @@
|
||||||
#ifndef MIDI_TRACK_H
|
|
||||||
#define MIDI_TRACK_H
|
|
||||||
|
|
||||||
#include "event.h"
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace Midi {
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Spec:
|
|
||||||
* MidiTrack = <midi_track_header_t> + <MidiEvent> [+ <MidiEvent> ...]
|
|
||||||
* midi_track_header_t = "MTrk" + <size (4 bytes)>
|
|
||||||
*
|
|
||||||
***/
|
|
||||||
|
|
||||||
typedef struct midi_track_header_t {
|
|
||||||
char cookie[4];
|
|
||||||
unsigned int size;
|
|
||||||
} __attribute__((packed)) midi_track_header_t;
|
|
||||||
|
|
||||||
|
|
||||||
class Track {
|
|
||||||
private:
|
|
||||||
unsigned int m_total_size;
|
|
||||||
midi_track_header_t m_header;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Track(void);
|
|
||||||
~Track(void);
|
|
||||||
void init_from_file(FILE *fp);
|
|
||||||
std::vector<Event *> m_events;
|
|
||||||
|
|
||||||
void print_yaml(){
|
|
||||||
printf(" - :header: MTrk\n");
|
|
||||||
printf(" :total_size: %u\n", m_header.size);
|
|
||||||
printf(" :events:\n");
|
|
||||||
|
|
||||||
for(int i = 0; i < m_events.size(); i++){
|
|
||||||
m_events[i]->print_yaml();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,204 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
require 'yaml'
|
|
||||||
|
|
||||||
class MidiToNES
|
|
||||||
|
|
||||||
#### Custom Exceptions
|
|
||||||
class MidiFormatNotSupported < StandardError; end
|
|
||||||
|
|
||||||
#### Some Constants
|
|
||||||
NoteOff = 0x8
|
|
||||||
NoteOn = 0x9
|
|
||||||
|
|
||||||
#### A440 Tuning, and NES CPU speed in hz
|
|
||||||
Tuning = 440.0
|
|
||||||
CPU = 1789773.0
|
|
||||||
|
|
||||||
|
|
||||||
#### LSB Address registers of the APU, MSB is always 0x40
|
|
||||||
Pulse1Control = 0x00
|
|
||||||
Pulse1FT = 0x2
|
|
||||||
Pulse1CT = 0x3
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Initialize from a yaml file
|
|
||||||
def self.init_from_file(filename, bpm)
|
|
||||||
self.new(File.read(filename), bpm)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Initialize with a yaml string
|
|
||||||
def initialize(yaml_string, bpm)
|
|
||||||
@bpm = bpm.to_f
|
|
||||||
@midi_data = YAML.load(yaml_string)[:midi_file]
|
|
||||||
unless @midi_data[:format].zero?
|
|
||||||
fail(MidiFormatNotSupported, "Currently only supports format 0 Midi Files")
|
|
||||||
end
|
|
||||||
@ticks_per_quarter_note = @midi_data[:ticks_per_quarter_note]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Write to binary file
|
|
||||||
def write_binary(filename)
|
|
||||||
binary = convert
|
|
||||||
File.open(filename, 'wb') do |fp|
|
|
||||||
fp.write(binary)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## For now assume one track
|
|
||||||
def convert
|
|
||||||
tick_count = 1
|
|
||||||
events = []
|
|
||||||
|
|
||||||
track = @midi_data[:tracks].first
|
|
||||||
track[:events].each do |event|
|
|
||||||
|
|
||||||
delta, status, note, velocity = event.values_at(:delta, :status, :parameter1, :parameter2)
|
|
||||||
|
|
||||||
## The status byte contains both the Midi message type, and channel.
|
|
||||||
type = (status & 0b11110000) >> 4
|
|
||||||
channel = status & 0b00001111
|
|
||||||
|
|
||||||
## We only care about note on and off, and only care about channel 0 for now.
|
|
||||||
next unless type == NoteOn || type == NoteOff
|
|
||||||
next unless channel.zero?
|
|
||||||
|
|
||||||
## Update the total time
|
|
||||||
tick_count += delta
|
|
||||||
|
|
||||||
## Ok this is a note either turning on or off
|
|
||||||
if type == NoteOff || velocity.zero?
|
|
||||||
#event = {:start => tick_count, :note => note, :velocity => 0}
|
|
||||||
#events << event
|
|
||||||
else
|
|
||||||
event = {:start => tick_count, :note => note, :velocity => velocity}
|
|
||||||
events << event
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
## Finally sort event list by start time
|
|
||||||
events.sort! do |a, b|
|
|
||||||
a[:start] <=> b[:start]
|
|
||||||
end
|
|
||||||
|
|
||||||
## Now convert these events to a bytestream for our NES sound engine
|
|
||||||
events_to_byte_stream(events)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## This converts a list of note events into a byte stream for updating NES APU registers
|
|
||||||
def events_to_byte_stream(events)
|
|
||||||
last_tick = 1
|
|
||||||
byte_stream = []
|
|
||||||
|
|
||||||
events.each do |event|
|
|
||||||
## Work out the delta again
|
|
||||||
delta = event[:start] - last_tick
|
|
||||||
byte_stream << midi_tick_to_vblank(delta) # Delta
|
|
||||||
byte_stream << pulse_control_value(event) # Value
|
|
||||||
if event[:velocity].zero?
|
|
||||||
#byte_stream << 0 # Off with 0 frequency timer
|
|
||||||
#byte_stream << 0
|
|
||||||
else
|
|
||||||
byte_stream << pulse_ft_value(event) # Value
|
|
||||||
byte_stream << pulse_ct_value(event) # Value
|
|
||||||
end
|
|
||||||
last_tick += delta
|
|
||||||
end
|
|
||||||
byte_stream.pack('C*')
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Given an event, produce a value for register nes.apu.pulse1.control
|
|
||||||
## DDLC VVVV
|
|
||||||
## Duty (D), envelope loop / length counter halt (L), constant volume (C), volume/envelope (V)
|
|
||||||
def pulse_control_value(event)
|
|
||||||
## Start with 50% duty cycle, length counter halt is on
|
|
||||||
## Constant volume is On, and volume is determined by bit-reducing the event velocity to 4-bit
|
|
||||||
value = 0b10000111
|
|
||||||
|
|
||||||
#four_bit_max = (2**4 - 1)
|
|
||||||
#seven_bit_max = (2**7 - 1)
|
|
||||||
|
|
||||||
#volume_float = event[:velocity] / seven_bit_max.to_f
|
|
||||||
#volume_4_bit = (volume_float * four_bit_max).round & 0b00001111
|
|
||||||
|
|
||||||
#value | volume_4_bit
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Given an event, produce a value for register nes.apu.pulse1.ft
|
|
||||||
## TTTT TTTT
|
|
||||||
## This is the low byte of the timer, the higher few bits being in pulse1.ct
|
|
||||||
def pulse_ft_value(event)
|
|
||||||
midi_note_to_nes_timer(event[:note]) & 0xff
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Given an event, produce a value for register nes.apu.pulse1.ct
|
|
||||||
## LLLL LTTT
|
|
||||||
## This has the higher 3 bits of the timer, and L is the length counter.
|
|
||||||
## For now let's just use duration as the length counter.
|
|
||||||
def pulse_ct_value(event)
|
|
||||||
value = 0b11111000
|
|
||||||
|
|
||||||
## We will grab the high 3 bits of the 11-bit timer value now
|
|
||||||
timer_high_3bit = midi_note_to_nes_timer(event[:note]) & 0b11100000000
|
|
||||||
value | (timer_high_3bit >> 8)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Midi note to NES timer
|
|
||||||
def midi_note_to_nes_timer(midi_note)
|
|
||||||
frequency = Tuning * 2**((midi_note - 69) / 12.0)
|
|
||||||
timer = (CPU / (16 * frequency)) - 1
|
|
||||||
if timer > (2**11 - 1)
|
|
||||||
fail("midi note #{midi_note} is too big at #{timer}")
|
|
||||||
end
|
|
||||||
timer.round
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Convert a MIDI tick delta to an NES vblank delta.
|
|
||||||
def midi_tick_to_vblank(midi_tick)
|
|
||||||
quarter_note_in_seconds = 60 / @bpm
|
|
||||||
vblanks_per_quarter_note = quarter_note_in_seconds / (1/60.0)
|
|
||||||
tick_normalized = midi_tick / @ticks_per_quarter_note.to_f
|
|
||||||
vblanks = tick_normalized * vblanks_per_quarter_note
|
|
||||||
vblanks.round
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
if __FILE__ == $0
|
|
||||||
unless ARGV.size == 2
|
|
||||||
STDERR.puts("Usage #{$0} <bpm> <music.mid>")
|
|
||||||
exit(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
bpm, midi_file = ARGV
|
|
||||||
|
|
||||||
## Run the midi file through my converter written in C++
|
|
||||||
IO.popen("./convert #{midi_file}") do |io|
|
|
||||||
midi_to_nes = MidiToNES.new(io.read, bpm.to_i)
|
|
||||||
midi_to_nes.write_binary('../../data.mus')
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
#include "file.h"
|
|
||||||
|
|
||||||
int main(int argc, char **argv){
|
|
||||||
|
|
||||||
if(argc < 1){
|
|
||||||
printf("Need a midi file argument\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Midi::File midi_file;
|
|
||||||
midi_file.init_from_file(argv[1]);
|
|
||||||
midi_file.print_yaml();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
#include "event.h"
|
|
||||||
|
|
||||||
namespace Midi {
|
|
||||||
|
|
||||||
////
|
|
||||||
// Default constructor
|
|
||||||
Event::Event(void) :
|
|
||||||
m_delta(0), m_size(0),
|
|
||||||
m_data_size(0), m_status(0),
|
|
||||||
m_meta_type(0), m_parameter1(0),
|
|
||||||
m_parameter2(0), m_data(NULL) { }
|
|
||||||
|
|
||||||
|
|
||||||
////
|
|
||||||
// Constructor to set specific fields
|
|
||||||
Event::Event(int delta, int status, int parameter1, int parameter2) :
|
|
||||||
m_delta(delta), m_size(4),
|
|
||||||
m_data_size(0), m_status(status),
|
|
||||||
m_meta_type(0), m_parameter1(parameter1),
|
|
||||||
m_parameter2(parameter2), m_data(NULL) { }
|
|
||||||
|
|
||||||
|
|
||||||
////
|
|
||||||
// Use a file pointer to initialize
|
|
||||||
void Event::init_from_file(FILE *fp, unsigned char last_status){
|
|
||||||
fpos_t saved_position;
|
|
||||||
unsigned int value_size;
|
|
||||||
// All types of events are preceeded by a uintvar delta time
|
|
||||||
m_delta = read_variable_length(fp, &value_size);
|
|
||||||
m_size += value_size;
|
|
||||||
|
|
||||||
// All types then have a status byte, unless they are reusing
|
|
||||||
// the previous status, get ready to rewind if this is the case.
|
|
||||||
fgetpos(fp, &saved_position);
|
|
||||||
fread(&m_status, sizeof(unsigned char), 1, fp);
|
|
||||||
m_size++;
|
|
||||||
|
|
||||||
if(!is_status_byte(m_status)){
|
|
||||||
// This is not a status byte, so it must be reusing the previous one, Rewind
|
|
||||||
m_status = last_status;
|
|
||||||
fsetpos(fp, &saved_position);
|
|
||||||
m_size--;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(m_status){
|
|
||||||
case 0xFF: // Meta
|
|
||||||
// This will have a meta sub type
|
|
||||||
fread(&m_meta_type, sizeof(unsigned char), 1, fp);
|
|
||||||
m_size++;
|
|
||||||
// Now a variable size for data
|
|
||||||
m_data_size = read_variable_length(fp, &value_size);
|
|
||||||
m_size += value_size;
|
|
||||||
// Finally, read the meta data
|
|
||||||
m_data = malloc(sizeof(unsigned char) * m_data_size);
|
|
||||||
fread(m_data, sizeof(unsigned char), m_data_size, fp);
|
|
||||||
m_size += m_data_size;
|
|
||||||
break;
|
|
||||||
case 0xF0: // Sysex
|
|
||||||
case 0xF7: // Sysex
|
|
||||||
// Sysex data runs until the next 0xF0 or 0xF7, count how many byte to allocate, and rewind
|
|
||||||
fgetpos(fp, &saved_position);
|
|
||||||
while(m_status != fgetc(fp)){
|
|
||||||
m_data_size++;
|
|
||||||
}
|
|
||||||
fsetpos(fp, &saved_position);
|
|
||||||
|
|
||||||
m_data = malloc(sizeof(unsigned char) * m_data_size);
|
|
||||||
fread(m_data, sizeof(unsigned char), m_data_size, fp);
|
|
||||||
m_size += (m_data_size + 1);
|
|
||||||
fgetc(fp); // Throw away the end byte, although it counts for size
|
|
||||||
break;
|
|
||||||
default: // Midi
|
|
||||||
fread(&m_parameter1, sizeof(unsigned char), 1, fp);
|
|
||||||
fread(&m_parameter2, sizeof(unsigned char), 1, fp);
|
|
||||||
m_size += 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Event::init_midi(int delta, int status, int parameter1, int parameter2){
|
|
||||||
m_delta = delta;
|
|
||||||
m_status = status;
|
|
||||||
m_parameter1 = parameter1;
|
|
||||||
m_parameter2 = parameter2;
|
|
||||||
m_size = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Event::~Event(void){
|
|
||||||
if(m_data){
|
|
||||||
free(m_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
#include "file.h"
|
|
||||||
|
|
||||||
namespace Midi {
|
|
||||||
|
|
||||||
File::File(void){
|
|
||||||
m_header.cookie[0] = m_header.cookie[1] = m_header.cookie[2] = m_header.cookie[3] = 0;
|
|
||||||
m_header.size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void File::init_from_file(const char *filename){
|
|
||||||
FILE *fp = fopen(filename, "rb");
|
|
||||||
fread(&m_header, sizeof(midi_header_t), 1, fp);
|
|
||||||
// Fix up this Big Endian stuff
|
|
||||||
m_header.size = swap_endian_32(m_header.size);
|
|
||||||
m_header.format = swap_endian_16(m_header.format);
|
|
||||||
m_header.track_count = swap_endian_16(m_header.track_count);
|
|
||||||
m_header.ticks_per_quarter_note = swap_endian_16(m_header.ticks_per_quarter_note);
|
|
||||||
|
|
||||||
// Read each track
|
|
||||||
Track *track;
|
|
||||||
for(int i = 0; i < m_header.track_count; i++){
|
|
||||||
track = new Track();
|
|
||||||
track->init_from_file(fp);
|
|
||||||
m_tracks.push_back(track);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
File::~File(void){
|
|
||||||
// Free all the midi tracks
|
|
||||||
for(unsigned int i = 0; i < m_tracks.size(); i++){
|
|
||||||
delete m_tracks[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
#include "helpers.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace Midi {
|
|
||||||
|
|
||||||
////
|
|
||||||
// I have eliminated any doubt that this function works on variable
|
|
||||||
// length uintvars up to 4 bytes.
|
|
||||||
unsigned int read_variable_length(FILE *fp, unsigned int *value_size) {
|
|
||||||
unsigned int value;
|
|
||||||
*value_size = 1;
|
|
||||||
|
|
||||||
if((value = fgetc(fp)) & 0x80){
|
|
||||||
unsigned char c;
|
|
||||||
value &= 0x7F;
|
|
||||||
do{
|
|
||||||
(*value_size)++;
|
|
||||||
value = (value << 7) + ((c = fgetc(fp)) & 0x7F);
|
|
||||||
} while (c & 0x80);
|
|
||||||
}
|
|
||||||
return(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
////
|
|
||||||
// Swap 4 bytes
|
|
||||||
int swap_endian_32(int big_endian){
|
|
||||||
register int little_endian;
|
|
||||||
little_endian = (big_endian & 0x000000FF);
|
|
||||||
little_endian = ((big_endian & 0x0000FF00) >> 0x08) | (little_endian << 0x08);
|
|
||||||
little_endian = ((big_endian & 0x00FF0000) >> 0x10) | (little_endian << 0x08);
|
|
||||||
little_endian = ((big_endian & 0xFF000000) >> 0x18) | (little_endian << 0x08);
|
|
||||||
return(little_endian);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
////
|
|
||||||
// Swap 2 bytes
|
|
||||||
short swap_endian_16(short big_endian){
|
|
||||||
register short little_endian;
|
|
||||||
little_endian = (big_endian & 0x00FF);
|
|
||||||
little_endian = ((big_endian & 0xFF00) >> 0x08) | (little_endian << 0x08);
|
|
||||||
return(little_endian);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
#include "track.h"
|
|
||||||
|
|
||||||
namespace Midi {
|
|
||||||
|
|
||||||
Track::Track(void){
|
|
||||||
m_header.cookie[0] = m_header.cookie[1] = m_header.cookie[2] = m_header.cookie[3] = 0;
|
|
||||||
m_header.size = 0;
|
|
||||||
m_total_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Track::init_from_file(FILE *fp){
|
|
||||||
Event *track_event;
|
|
||||||
unsigned int bytes_read = 0;
|
|
||||||
unsigned char last_status = 0x0;
|
|
||||||
|
|
||||||
fread(&m_header, sizeof(midi_track_header_t), 1, fp);
|
|
||||||
m_header.size = swap_endian_32(m_header.size);
|
|
||||||
if(m_header.size == 0) return;
|
|
||||||
|
|
||||||
while(bytes_read < m_header.size){
|
|
||||||
track_event = new Event();
|
|
||||||
track_event->init_from_file(fp, last_status);
|
|
||||||
m_events.push_back(track_event);
|
|
||||||
last_status = track_event->status();
|
|
||||||
bytes_read += track_event->bytes_read();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Track::~Track(void){
|
|
||||||
// Free all the track events
|
|
||||||
for(unsigned int i = 0; i < m_events.size(); i++){
|
|
||||||
delete m_events[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user