mirror of
https://github.com/safiire/n65.git
synced 2024-12-11 08:49:42 +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
|
||||
case @value
|
||||
when Fixnum
|
||||
when Integer
|
||||
bytes = [@value & 0xFFFF].pack('S').bytes
|
||||
assembler.write_memory(bytes)
|
||||
when String
|
||||
|
@ -57,7 +57,7 @@ module N65
|
||||
:example => 'AAA $FF, Y',
|
||||
:display => '%s $%.2X, Y',
|
||||
: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 => {
|
||||
@ -220,7 +220,7 @@ module N65
|
||||
end
|
||||
|
||||
case @arg
|
||||
when Fixnum, NilClass
|
||||
when Integer, NilClass
|
||||
assembler.write_memory(emit_bytes)
|
||||
when String
|
||||
begin
|
||||
|
@ -1,3 +1,3 @@
|
||||
module N65
|
||||
VERSION = "0.5.0"
|
||||
VERSION ||= "1.0.0"
|
||||
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