From: Yuxuan Shui yshui@codeweavers.com
--- dlls/dmime/Makefile.in | 1 + dlls/dmime/miditracks.c | 499 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 500 insertions(+) create mode 100644 dlls/dmime/miditracks.c
diff --git a/dlls/dmime/Makefile.in b/dlls/dmime/Makefile.in index cf041ef2b27..fe7aa7df272 100644 --- a/dlls/dmime/Makefile.in +++ b/dlls/dmime/Makefile.in @@ -10,6 +10,7 @@ SOURCES = \ graph.c \ lyricstrack.c \ markertrack.c \ + miditracks.c \ paramcontroltrack.c \ performance.c \ segment.c \ diff --git a/dlls/dmime/miditracks.c b/dlls/dmime/miditracks.c new file mode 100644 index 00000000000..8dc0a645247 --- /dev/null +++ b/dlls/dmime/miditracks.c @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2024 Yuxuan Shui for CodeWeavers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "dmime_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(dmime); + +union event_data +{ + struct + { + BYTE byte1; + BYTE byte2; + BYTE byte3; + BYTE byte4; + BYTE byte5; + } fixed; + UINT integer; + struct + { + SIZE_T size; + BYTE *data; + } variable; +}; + +enum meta_event_type +{ + MIDI_META_SEQUENCE_NUMBER = 0x00, + MIDI_META_TEXT_EVENT = 0x01, + MIDI_META_COPYRIGHT_NOTICE = 0x02, + MIDI_META_TRACK_NAME = 0x03, + MIDI_META_INSTRUMENT_NAME = 0x04, + MIDI_META_LYRIC = 0x05, + MIDI_META_MARKER = 0x06, + MIDI_META_CUE_POINT = 0x07, + MIDI_META_CHANNEL_PREFIX_ASSIGNMENT = 0x20, + MIDI_META_END_OF_TRACK = 0x2f, + MIDI_META_SET_TEMPO = 0x51, + MIDI_META_SMPTE_OFFSET = 0x54, + MIDI_META_TIME_SIGNATURE = 0x58, + MIDI_META_KEY_SIGNATURE = 0x59, + MIDI_META_SEQUENCER_SPECIFIC = 0x7f +}; + +struct event +{ + UINT deltaTime; + BYTE status; + /* type of meta events. */ + enum meta_event_type type; + union event_data data; +}; + +struct midi_sequence_track +{ + IDirectMusicTrack8 IDirectMusicTrack8_iface; + struct dmobject dmobj; /* IPersistStream only */ + LONG ref; + + /* MIDI deltaTime can be either tempo dependent or not. If + * ticks_per_quarter_note is 0, then ticks_per_second should be used. + * Converting MIDI time to DirectMusic MUSIC_TIME is complicated. MUSIC_TIME + * is number of quarter notes times DMUS_PPQ, this is tempo dependent. If + * MIDI time is tempo dependent as well, then we just need to multiply it by + * a factor (because MIDI can have a different division of quarter notes). + * + * If MIDI time is not tempo dependent however, the conversion would be + * difficult because tempo information can be stored in another track. And + * we need the tempo information to convert MIDI time to MUSIC_TIME. The + * only reasonable way to do this is to report a "fake", fixed tempo, which + * makes MUSIC_TIME and MIDI time the same. + */ + UINT ticks_per_quarter_note; + UINT ticks_per_second; + UINT event_count; + + struct event events[]; +}; + +C_ASSERT(sizeof(struct midi_sequence_track) == + offsetof(struct midi_sequence_track, events[0])); + +static inline struct midi_sequence_track * +impl_from_IDirectMusicTrack8(IDirectMusicTrack8 *iface) +{ + return CONTAINING_RECORD(iface, struct midi_sequence_track, + IDirectMusicTrack8_iface); +} + +static void free_event(struct event *event) +{ + if (event->status == 0xff) + { + switch (event->type) + { + case MIDI_META_SEQUENCE_NUMBER: + case MIDI_META_CHANNEL_PREFIX_ASSIGNMENT: + case MIDI_META_END_OF_TRACK: + case MIDI_META_SET_TEMPO: + case MIDI_META_SMPTE_OFFSET: + case MIDI_META_TIME_SIGNATURE: + case MIDI_META_KEY_SIGNATURE: + break; + + case MIDI_META_TEXT_EVENT: + case MIDI_META_COPYRIGHT_NOTICE: + case MIDI_META_TRACK_NAME: + case MIDI_META_INSTRUMENT_NAME: + case MIDI_META_LYRIC: + case MIDI_META_MARKER: + case MIDI_META_CUE_POINT: + case MIDI_META_SEQUENCER_SPECIFIC: + free(event->data.variable.data); + break; + default: + ERR("Unknown meta event type %02x\n", event->type); + } + } +} + +static HRESULT WINAPI midi_sequence_track_QueryInterface( + IDirectMusicTrack8 *iface, REFIID riid, void **ret_iface) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %p)\n", This, debugstr_dmguid(riid), ret_iface); + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IDirectMusicTrack) || + IsEqualIID(riid, &IID_IDirectMusicTrack8)) + *ret_iface = &This->IDirectMusicTrack8_iface; + else if (IsEqualIID(riid, &IID_IPersistStream)) + *ret_iface = &This->dmobj.IPersistStream_iface; + else + { + *ret_iface = NULL; + WARN("(%p, %s, %p): not found\n", This, debugstr_dmguid(riid), + ret_iface); + return E_NOINTERFACE; + } + + IDirectMusicTrack8_AddRef(iface); + return S_OK; +} + +static ULONG WINAPI midi_sequence_track_AddRef(IDirectMusicTrack8 *iface) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + ULONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p): %lu\n", This, ref); + + return ref; +} + +static ULONG WINAPI midi_sequence_track_Release(IDirectMusicTrack8 *iface) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + ULONG ref = InterlockedDecrement(&This->ref), i; + + TRACE("(%p): %lu\n", This, ref); + + if (!ref) + { + for (i = 0; i < This->event_count; i++) + free_event(This->events + i); + free(This); + } + + return ref; +} + +static HRESULT WINAPI midi_sequence_track_Init(IDirectMusicTrack8 *iface, + IDirectMusicSegment *pSegment) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p): stub\n", This, pSegment); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_InitPlay( + IDirectMusicTrack8 *iface, IDirectMusicSegmentState *pSegmentState, + IDirectMusicPerformance *pPerformance, void **ppStateData, + DWORD dwVirtualTrack8ID, DWORD dwFlags) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p, %p, %p, %ld, %ld): stub\n", This, pSegmentState, + pPerformance, ppStateData, dwVirtualTrack8ID, dwFlags); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_EndPlay(IDirectMusicTrack8 *iface, + void *pStateData) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p): stub\n", This, pStateData); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_Play( + IDirectMusicTrack8 *iface, void *state_data, MUSIC_TIME start_time, + MUSIC_TIME end_time, MUSIC_TIME time_offset, DWORD segment_flags, + IDirectMusicPerformance *performance, + IDirectMusicSegmentState *segment_state, DWORD track_id) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + IDirectMusicGraph *graph; + HRESULT hr; + UINT i; + /* # of quarter notes is the most reasonable common units of time between + * MIDI and other parts of DirectMusic. */ + double number_of_quarter_notes = 0, usec_per_quarter_note = 500000; + /* reference time is in 100 nanoseconds units, or 10 million ticks per + * second. */ + double ref_time = 0; + + TRACE("(%p, %p, %ld, %ld, %ld, %#lx, %p, %p, %ld)\n", This, state_data, + start_time, end_time, time_offset, segment_flags, performance, + segment_state, track_id); + + if (segment_flags) + FIXME("segment_flags %#lx not implemented\n", segment_flags); + if (segment_state) + FIXME("segment_state %p not implemented\n", segment_state); + + if (FAILED(hr = IDirectMusicPerformance_QueryInterface( + performance, &IID_IDirectMusicGraph, (void **)&graph))) + return hr; + + for (i = 0; SUCCEEDED(hr) && i < This->event_count; i++) + { + struct event *item = This->events + i; + DMUS_MIDI_PMSG *msg; + + if (This->ticks_per_quarter_note) + { + ref_time += item->deltaTime * usec_per_quarter_note * 10 / + This->ticks_per_quarter_note; + number_of_quarter_notes += + (double)item->deltaTime / This->ticks_per_quarter_note; + } + else + { + double elapsed = + item->deltaTime * 1000000.0 / This->ticks_per_second; + ref_time += elapsed; + number_of_quarter_notes += elapsed / 10 / usec_per_quarter_note; + } + + if (item->status == 0xff) + { + hr = handle_meta_events(This, item, &usec_per_quarter_note); + if (hr != S_OK) + break; + continue; + } + + if ((MUSIC_TIME)number_of_quarter_notes < + (start_time + DMUS_PPQ - 1) / DMUS_PPQ) + continue; + if ((MUSIC_TIME)number_of_quarter_notes >= end_time / DMUS_PPQ) + continue; + + if (FAILED(hr = IDirectMusicPerformance_AllocPMsg( + performance, sizeof(*msg), (DMUS_PMSG **)&msg))) + break; + + msg->dwFlags = DMUS_PMSGF_REFTIME; + msg->rtTime = (REFERENCE_TIME)ref_time; + msg->dwPChannel = item->status & 0xf; + msg->dwVirtualTrackID = track_id; + msg->dwType = DMUS_PMSGT_MIDI; + msg->dwGroupID = 1; + msg->bStatus = item->status; + msg->bByte1 = item->data.fixed.byte1; + msg->bByte2 = item->data.fixed.byte2; + + if (FAILED(hr = IDirectMusicGraph_StampPMsg(graph, (DMUS_PMSG *)msg)) || + FAILED(hr = IDirectMusicPerformance_SendPMsg(performance, + (DMUS_PMSG *)msg))) + { + IDirectMusicPerformance_FreePMsg(performance, (DMUS_PMSG *)msg); + break; + } + } + + return SUCCEEDED(hr) ? S_OK : hr; +} + +static HRESULT WINAPI midi_sequence_track_GetParam(IDirectMusicTrack8 *iface, + REFGUID type, + MUSIC_TIME time, + MUSIC_TIME *next, + void *param) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %ld, %p, %p): method not implemented\n", This, + debugstr_dmguid(type), time, next, param); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_SetParam(IDirectMusicTrack8 *iface, + REFGUID type, + MUSIC_TIME time, void *param) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %ld, %p): method not implemented\n", This, + debugstr_dmguid(type), time, param); + return E_NOTIMPL; +} + +static HRESULT WINAPI +midi_sequence_track_IsParamSupported(IDirectMusicTrack8 *iface, REFGUID type) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s): method not implemented\n", This, debugstr_dmguid(type)); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_AddNotificationType( + IDirectMusicTrack8 *iface, REFGUID notiftype) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s): method not implemented\n", This, + debugstr_dmguid(notiftype)); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_RemoveNotificationType( + IDirectMusicTrack8 *iface, REFGUID notiftype) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s): method not implemented\n", This, + debugstr_dmguid(notiftype)); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_Clone(IDirectMusicTrack8 *iface, + MUSIC_TIME mtStart, + MUSIC_TIME mtEnd, + IDirectMusicTrack **ppTrack) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %ld, %ld, %p): stub\n", This, mtStart, mtEnd, ppTrack); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_PlayEx( + IDirectMusicTrack8 *iface, void *pStateData, REFERENCE_TIME rtStart, + REFERENCE_TIME rtEnd, REFERENCE_TIME rtOffset, DWORD dwFlags, + IDirectMusicPerformance *pPerf, IDirectMusicSegmentState *pSegSt, + DWORD dwVirtualID) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + FIXME("(%p, %p, 0x%s, 0x%s, 0x%s, %ld, %p, %p, %ld): stub\n", This, + pStateData, wine_dbgstr_longlong(rtStart), + wine_dbgstr_longlong(rtEnd), wine_dbgstr_longlong(rtOffset), dwFlags, + pPerf, pSegSt, dwVirtualID); + return S_OK; +} + +static HRESULT WINAPI midi_sequence_track_GetParamEx( + IDirectMusicTrack8 *iface, REFGUID type, REFERENCE_TIME time, + REFERENCE_TIME *next, void *param, void *state, DWORD flags) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %s, %p, %p, %p, %lx): method not implemented\n", This, + debugstr_dmguid(type), wine_dbgstr_longlong(time), next, param, state, + flags); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_SetParamEx(IDirectMusicTrack8 *iface, + REFGUID type, + REFERENCE_TIME time, + void *param, void *state, + DWORD flags) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %s, %s, %p, %p, %lx): method not implemented\n", This, + debugstr_dmguid(type), wine_dbgstr_longlong(time), param, state, + flags); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_Compose(IDirectMusicTrack8 *iface, + IUnknown *context, + DWORD trackgroup, + IDirectMusicTrack **track) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + + TRACE("(%p, %p, %ld, %p): method not implemented\n", This, context, + trackgroup, track); + return E_NOTIMPL; +} + +static HRESULT WINAPI midi_sequence_track_Join( + IDirectMusicTrack8 *iface, IDirectMusicTrack *newtrack, MUSIC_TIME join, + IUnknown *context, DWORD trackgroup, IDirectMusicTrack **resulttrack) +{ + struct midi_sequence_track *This = impl_from_IDirectMusicTrack8(iface); + TRACE("(%p, %p, %ld, %p, %ld, %p): method not implemented\n", This, + newtrack, join, context, trackgroup, resulttrack); + return E_NOTIMPL; +} +struct IDirectMusicTrack8Vtbl midi_sequence_track_vtbl = +{ + midi_sequence_track_QueryInterface, + midi_sequence_track_AddRef, + midi_sequence_track_Release, + midi_sequence_track_Init, + midi_sequence_track_InitPlay, + midi_sequence_track_EndPlay, + midi_sequence_track_Play, + midi_sequence_track_GetParam, + midi_sequence_track_SetParam, + midi_sequence_track_IsParamSupported, + midi_sequence_track_AddNotificationType, + midi_sequence_track_RemoveNotificationType, + midi_sequence_track_Clone, + midi_sequence_track_PlayEx, + midi_sequence_track_GetParamEx, + midi_sequence_track_SetParamEx, + midi_sequence_track_Compose, + midi_sequence_track_Join +}; + +static inline struct midi_sequence_track * +impl_from_IPersistStream(IPersistStream *iface) +{ + return CONTAINING_RECORD(iface, struct midi_sequence_track, + dmobj.IPersistStream_iface); +} + +static HRESULT WINAPI track_IPersistStream_Load(IPersistStream *iface, + IStream *stream) +{ + struct midi_sequence_track *This = impl_from_IPersistStream(iface); + + TRACE("(%p, %p): stub\n", This, stream); + return S_OK; +} + +static const IPersistStreamVtbl midi_sequence_track_persiststream_vtbl = +{ + dmobj_IPersistStream_QueryInterface, + dmobj_IPersistStream_AddRef, + dmobj_IPersistStream_Release, + dmobj_IPersistStream_GetClassID, + unimpl_IPersistStream_IsDirty, + track_IPersistStream_Load, + unimpl_IPersistStream_Save, + unimpl_IPersistStream_GetSizeMax +}; + +HRESULT create_midiseqtrack(REFIID riid, void **ppobj) +{ + struct midi_sequence_track *track; + HRESULT hr; + + *ppobj = NULL; + if (!(track = calloc(1, sizeof(*track)))) + return E_OUTOFMEMORY; + track->IDirectMusicTrack8_iface.lpVtbl = &midi_sequence_track_vtbl; + track->ref = 1; + dmobject_init(&track->dmobj, &CLSID_DirectMusicSeqTrack, + (IUnknown *)&track->IDirectMusicTrack8_iface); + track->dmobj.IPersistStream_iface.lpVtbl = + &midi_sequence_track_persiststream_vtbl; + + hr = IDirectMusicTrack8_QueryInterface(&track->IDirectMusicTrack8_iface, + riid, ppobj); + IDirectMusicTrack8_Release(&track->IDirectMusicTrack8_iface); + + return hr; +}